Juliaの構造体とジェネリクスについていろいろ勘違いしていた話

Juliaについて

最近、数値計算用のプログラミング言語、Juliaにはまっています。
Juliaは、動的型付けでありながら、ユーザ(プログラマ側)が型を明示したり、関数単位のJIT(Just-In-Time)コンパイルに伴う最適化によって高速に実行可能であるため、注目されています。

Juliaにおいても、多言語のジェネリクスのように静的型付け言語にあるようなサブタイプ、スーパータイプを指定することによる型の制約を設けることが可能です。 (2018年5月29日時点、Juliaのバージョンは0.6) 今回はJuliaの構造体とジェネリクス(?)に少々はまったので記しています。

コンストラクタにおける勘違い

まず、以下のようなコードを考えます。
ここで、TはAbstractFloatのサブタイプであることを意味しており、 Hoge{T}(_a::T) = new(_a) はコンストラクタの定義を表しています。 また、AbstractFloatは64bit浮動小数型であるFloat64、32bit浮動小数型であるFloat32のスーパータイプとなっています。

struct Hoge{T<: AbstractFloat}
      a::T
      Hoge(_a::T) = new(_a)
end

このコードを実行すると、型パラメータと制約を明記するよう、警告されます。 Juliaでは型パラメータTをそのまま利用できるわけではないようです。これは 構造体の定義外にもコンストラクタを定義できることから、構文を統一されるためのもの だと推測しています。
(Juliaのドキュメントでは、コンストラクタの定義位置が構造体定義の内外に応じて、Innner、Outerと区別されている)

WARNING: deprecated syntax "inner constructor Hoge(...) around C:\ProjectYukito\julia\famiconlike_sound_spectrum.jl:21".
Use "Hoge{T}(...) where T" instead.

そこで、次のように変更します。これにより、警告が無くなりました。

struct Hoge{T<:AbstractFloat}
    a::T
    Hoge{U}(_a::U) where U<:AbstractFloat = new(_a)
end

ジェネリクスにおける勘違い

次に、Hoge型の変数を宣言することを考えます。

hoge = Hoge(0.5)

一見問題ないように見えますが、 このコードは、以下のエラーのため実行できません。

ERROR: LoadError: MethodError: Cannot `convert` an object of type Float64 to an object of type Hoge
This may have arisen from a call to the constructor Hoge(...),
since type constructors fall back to convert methods.

=コンストラクタがconvert関数として扱われるが、convert関数がFloat64→Hogeの変換に対応していない(?)

コンストラクタが引数の型に対応していない場合、convert関数でエラーを出力することは型変換に関するJuliaのドキュメントにも記されています。

Conversion and Promotion · The Julia Language

問題は、 Float64に対してHogeのコンストラクタが対応していない点 です。
Float64はAbstractFloatのサブタイプであるはずなので、エラーが出力されることが解せませんでした。そこで、Float64であることを明示してみます。

hoge = Hoge{Float64}(0.5)

これにより、初めて正常に実行されました。 ちなみに、Float64の代わりにAbstractFloatのサブタイプであるFloat32を指定すると、同様に cannnot convert ... のエラーが出力されます。一方で、AbstractFloatのサブタイプでない型(UInt32など)を指定すると、型制約に伴うエラーが出力されます。

これらのことから、 Juliaでは、引数の型に応じて推論の結果型パラメータの値が定まるのではなく、型を明示する必要がある という結論に至っています。

まとめ

普段Rustばかり書いているせいか、Juliaのジェネリクスについて混乱してしまいました。

  • 内部コンストラクタ(Innner-Constructor)において、構造体の定義に用いる型パラメータは直接適用されない
  • 変数の宣言時、型パラメータは明示する必要がある(引数の型推論から確定するわけではない?)