Haskell:Typeclass vs関数を渡す


16

私には、型クラスを使用するのではなく、常に関数の引数を渡すことができるようです。たとえば、等価型クラスを定義するのではなく、

class Eq a where 
  (==)                  :: a -> a -> Bool

そして、型引数を示すために他の関数でそれを使用することはのインスタンスでなければならないEq

elem                    :: (Eq a) => a -> [a] -> Bool

elemタイプクラスを使用せずに関数を定義し、代わりに機能する関数引数を渡すことはできませんか?


2
これは辞書受け渡しと呼ばれます。タイプクラス制約は暗黙の引数と考えることができます。
Poscat

2
あなたはそれを行うことができますが、関数を渡す必要がなく、タイプに応じて「標準の」関数を使用するほうがはるかに便利です。
ロビンジグモンド

2
そういう風に言えますね。しかし、私は少なくとも1つの他の重要な利点があると主張します:特定の「インターフェース」または機能のセットを実装する任意のタイプで機能する多態性関数を書く能力。タイプクラスの制約は、追加の関数引数を渡さない方法で、それを非常に明確に表現していると思います。特に、多くのタイプクラスが満たさなければならない(悲しいことに)「法則」があるためです。Monad m制約は、より多くの私には種類の追加の関数の引数を渡すよりも言いますa -> m am a -> (a -> m b) -> m b
ロビンジグモンド

1
ルキ

1
TypeApplications拡張機能を使用すると、暗黙の引数を明示的に行うことができます。 (==) @Int 3 5比較3して5、具体的Intな値。固有の比較関数自体@Intではなく、型固有の等価関数のディクショナリのキーと考えることができますInt
chepner

回答:


19

はい。これを「辞書引きスタイル」といいます。時々私が特にトリッキーなことをしているとき、私は型クラスをスクラップして辞書に変換する必要があります。辞書の受け渡しはより強力ですが1、しばしばとても面倒で、概念的に単純なコードはかなり複雑に見えます。私は、Haskell以外の言語で辞書受け渡しスタイルを使用してタイプクラスをシミュレートします(ただし、通常、これは思ったほど優れたアイデアではないことを学びました)。

もちろん、表現力に違いがある場合は常にトレードオフがあります。特定のAPIがDPSを使用して記述されている場合は、より多くの方法で使用できますが、できない場合はAPIがより多くの情報を取得します。これが実際に現れる1つの方法はですData.Set。これは、Ordタイプごとに1 つの辞書しかないという事実に依存しています。Set店舗は、その要素は、に従ってソートOrd使用すると、1つの辞書とのセットを構築し、別のものを使用して要素を挿入し、DPSで可能なように、あなたは破ることができれば、とSetの不変、それがクラッシュします。この一意性の問題は、ファントム存在を使用して軽減できます。辞書をマークするために入力しますが、やはり、APIがかなり煩わしく複雑になります。これは、TypeableAPI でもほぼ同じように表示されます。

一意性のビットはあまり出てきません。タイプクラスが優れているのは、コードを作成することです。例えば、

catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g

これは、入力を受け取り、出力を提供する可能性のある2つの「プロセッサ」を受け取り、それらを連結して平坦化しNothing、DPSで次のように記述する必要があります。

catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g

型のシグネチャで既にスペルアウトしているにもかかわらず、基本的には、使用している型をもう一度スペルアウトする必要があり、コンパイラーがすべてのタイプを認識しているため、冗長でした。Semigroup型で指定されたものを構築する方法は1つしかないため、コンパイラーがそれを行うことができます。これは、Data.Functor.*コンビネーターのように、多くのパラメトリックインスタンスの定義を開始し、タイプの構造を使用して計算を開始するときに、「複利」タイプの効果をもたらします。これは、deriving via基本的にすべてを取得できる場所で大きな効果を発揮するために使用されますあなたのために書かれたあなたのタイプの「標準」代数構造。

また、タイプチェックと推論に情報をフィードバックするMPTCとFundepsについても説明しません。私はそのようなことをDPSに変換しようとしたことがありません-それは多くの型の等価性の証明を通過することを含むと思います-とにかく、私は快適になるよりも私の脳のための多くの仕事になると確信していますと。

-

1 Uは、あなたが使用nless reflectionしかし-彼らは力に同等になった場合にreflectionも、使用に面倒なことができます。



DPSで表現されるFundepsにとても興味があります。このテーマに関するいくつかの推奨可能なリソースを知っていますか?とにかく、とても分かりやすい説明。
ボブ

@bob、オフハンドではありませんが、興味深い調査になります。それについて新しい質問をするかもしれませんか?
ルキ

5

はい。これは、基本的にはコンパイラーが型クラスに対して行うことです(ディクショナリ受け渡しと呼ばれます)。その関数は、文字通りに実行すると、次のようになります。

elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool
elemBy _ _ [] = False
elemBy eq x (y:ys) = eq x y || elemBy eq x ys

呼び出しelemBy (==) x xsはと同等になりましたelem x xs。そして、この特定のケースでeqは、さらに一歩進めることができます。最初の引数は毎回同じなので、それを適用するのは呼び出し側の責任であり、最終的には次のようになります。

elemBy2 :: (a -> Bool) -> [a] -> Bool
elemBy2 _ [] = False
elemBy2 eqx (y:ys) = eqx y || elemBy2 eqx ys

呼び出しelemBy2 (x ==) xsはと同等になりましたelem x xs

...あ、待って。それだけanyです。(そして実際、標準ライブラリでは、elem = any . (==)


AFAIU辞書の受け渡しは、型クラスをエンコードするScalaのアプローチです。これらの追加の引数は次のように宣言できimplicit、コンパイラはスコープからそれらを挿入します。
michid
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.