はい、表面的には非常に簡単な質問です。しかし、時間をかけて最後まで考えると、計り知れない型理論の深みに入ります。そして、型理論はあなたにも凝視します。
最初に、もちろん、F#には型クラスがないことをすでに正しく理解しているので、そのためです。しかし、あなたはインターフェースを提案しますMappable
。それでは、調べてみましょう。
そのようなインターフェースを宣言できるとしましょう。それの署名がどのようになるか想像できますか?
type Mappable =
abstract member map : ('a -> 'b) -> 'f<'a> -> 'f<'b>
f
インターフェースを実装するタイプはどこですか。あ、待って!F#にもありません!ここにf
種類の高い変数があり、F#には種類がまったくありません。関数f : 'm<'a> -> 'm<'b>
やそのようなものを宣言する方法はありません。
しかし、OK、そのハードルも乗り越えたとしましょう。そして今、我々はインターフェイス持っているMappable
ことで実現できるList
、Array
、Seq
、、キッチンシンクを。ちょっと待って!今、私たちは持っているメソッドの代わりに関数のを、および方法はうまく構成されていません!ネストされたリストのすべての要素に42を追加してみましょう。
// Good ol' functions:
add42 nestedList = nestedList |> List.map (List.map ((+) 42))
// Using an interface:
add42 nestedList = nestedList.map (fun l -> l.map ((+) 42))
見てください:ラムダ式を使用する必要があります!この.map
実装を別の関数に値として渡す方法はありません。事実上、「値としての関数」の終了(そしてもちろん、ラムダを使用しても、この例ではそれほど見栄えがよくありませんが、信じてください、とても醜くなります)
しかし、待ってください。まだ完了していません。メソッド呼び出しなので、型推論は機能しません!.NETメソッドの型シグネチャはオブジェクトの型に依存するため、コンパイラが両方を推測する方法はありません。これは実際、初心者が.NETライブラリと相互運用するときに直面する非常に一般的な問題です。そして唯一の解決策は型シグネチャを提供することです:
add42 (nestedList : #Mappable) = nestedList.map (fun l -> l.map ((+) 42))
ああ、でもこれではまだ十分ではありません!私はnestedList
それ自体の署名を提供しましたが、ラムダのパラメーターの署名は提供していませんl
。そのような署名は何であるべきですか?あるべきだとfun (l: #Mappable) -> ...
思いますか?ああ、今あなたが見るために、我々は最終的には、ランクN型になった、#Mappable
「あらゆるタイプのショートカットです'a
ように'a :> Mappable
、一般的な、それ自体、すなわちAラムダ表現- 」。
または、別の方法として、より親切に戻り、タイプをnestedList
より正確に宣言することもできます。
add42 (nestedList : 'f<'a<'b>> where 'f :> Mappable, 'a :> Mappable) = ...
しかし、わかりました。今のところ、型推論はさておき、ラムダ式に戻って、map
別の関数に値として渡すことができない方法に戻りましょう。構文を少し拡張して、Elmがレコードフィールドで行うようなことを許可するとします。
add42 nestedList = nestedList.map (.map ((+) 42))
どのようなタイプ.map
ですか?Haskellと同じように、制約された型でなければなりません!
.map : Mappable 'f => ('a -> 'b) -> 'f<'a> -> 'f<'b>
わあ、わかった。.NETではそのような型が存在することさえできないという事実はさておき、事実上、型クラスを取り戻しただけです。
しかし、そもそもF#に型クラスがないのには理由があります。その理由の多くの側面が上で説明されていますが、より簡潔に言うと、単純さです。
ご覧のとおり、これは糸の玉です。型クラスを取得したら、制約、親切性、ランクN(または少なくともランク2)を設定する必要があります。また、それを知る前に、命令型、型関数、GADT、およびすべてのそれの残り。
しかし、Haskellはすべてのグッズに代価を払っています。これらすべてを推測する良い方法はないことがわかりました。種類の高いタイプは機能しますが、制約はすでに機能していません。ランクN-夢さえしないでください。そして、それが機能しても、理解するために博士号を取得しなければならないタイプエラーが発生します。そして、それがHaskell ですべてに型シグネチャを付けることを優しく勧めている理由です。まあ、すべてではない- すべてではなく、本当にほとんどすべて。また、型シグネチャを配置しない場合(例:let
とwhere
)-サプライズ-サプライズ、これらの場所は実際にはモノモーフィングされているため、基本的には単純なF#ランドに戻ります。
一方、F#では、型シグネチャはまれであり、ほとんどがドキュメントまたは.NET相互運用のためだけです。これらの2つのケースの外では、F#で大きな複雑なプログラム全体を記述して、型シグネチャを1回使用する必要はありません。型推論は、複雑すぎたりあいまいであったりして処理できないため、うまく機能します。
そして、これはHaskellよりもF#の大きな利点です。はい、Haskellを使用すると、非常に正確な方法で超複雑なものを表現できます。しかし、F#を使用すると、ほとんどPythonやRubyのように非常に希望に満ちた態度を保つことができます。