F#のさまざまな型に対応する `map`関数がたくさんあるのはなぜですか


9

F#を学習しています。私はHaskellでFPを開始しましたが、これに興味があります。

F#は.NET言語であるためMappable、haskell Functor型クラスと同様に、のようなインターフェイスを宣言する方が合理的であるようです。

ここに画像の説明を入力してください

しかし、上の図のように、F#関数は分離されており、独自に実装されています。そのようなデザインのデザイン目的は何ですか?私にとっては、Mappable.mapこれを各データ型に導入して実装する方が便利です。


この質問はSOに属していません。プログラミングの問題ではありません。F#Slackまたはその他のディスカッションフォーラムで質問することをお勧めします。
ベントトランバーグ

5
@BentTranberg The community is here to help you with specific coding, algorithm, or language problems.他の基準が満たされている限り、寛大に読むと、言語設計の質問も含まれます。
kaefer

3
要するに、F#には型クラスがないmapため、各コレクション型に対して再実装やその他の一般的な高次関数を使用する必要があります。インターフェイスは、各コレクション型が個別の実装を提供する必要があるため、ほとんど役に立ちません。
dumetrulo

回答:


19

はい、表面的には非常に簡単な質問です。しかし、時間をかけて最後まで考えると、計り知れない型理論の深みに入ります。そして、型理論はあなたにも凝視します。

最初に、もちろん、F#には型クラスがないことをすでに正しく理解しているので、そのためです。しかし、あなたはインターフェースを提案しますMappable。それでは、調べてみましょう。

そのようなインターフェースを宣言できるとしましょう。それの署名がどのようになるか想像できますか?

type Mappable =
    abstract member map : ('a -> 'b) -> 'f<'a> -> 'f<'b>

fインターフェースを実装するタイプはどこですか。あ、待って!F#にもありません!ここにf種類の高い変数があり、F#には種類がまったくありません。関数f : 'm<'a> -> 'm<'b>やそのようなものを宣言する方法はありません。

しかし、OK、そのハードルも乗り越えたとしましょう。そして今、我々はインターフェイス持っているMappableことで実現できるListArraySeq、、キッチンシンクを。ちょっと待って!今、私たちは持っているメソッドの代わりに関数のを、および方法はうまく構成されていません!ネストされたリストのすべての要素に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 ですべてに型シグネチャを付けることを優しく勧めている理由です。まあ、すべてではない- すべてではなく、本当にほとんどすべて。また、型シグネチャを配置しない場合(例:letwhere)-サプライズ-サプライズ、これらの場所は実際にはモノモーフィングされているため、基本的には単純なF#ランドに戻ります。

一方、F#では、型シグネチャはまれであり、ほとんどがドキュメントまたは.NET相互運用のためだけです。これらの2つのケースの外では、F#で大きな複雑なプログラム全体を記述して、型シグネチャを1回使用する必要はありません。型推論は、複雑すぎたりあいまいであったりして処理できないため、うまく機能します。

そして、これはHaskellよりもF#の大きな利点です。はい、Haskellを使用すると、非常に正確な方法で超複雑なものを表現できます。しかし、F#を使用すると、ほとんどPythonやRubyのように非常に希望に満ちた態度を保つことができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.