2016-07-28 10 views
5

TypeFamilies,FunctionalDependenciesMultiParamTypeClassesで遊んでいます。そして、それはあたかもTypeFamiliesのように、他の2つの具体的な機能を追加していないようです。 (しかしその逆ではない)。しかし、タイプファミリーはかなり好きなので、私は何かが欠けているように感じます。マルチプラム型のクラスと関数の依存関係ができないタイプファミリは何ですか

TypeFamiliesでは可能ではないと思われる変換機能のようなタイプ間の「オープン」関係。通常、標準型ファミリーで行われるであろうようなタイプ安全な擬似アヒルタイピング機構の一種であるタイプ、間

class Convert a b where 
    convert :: a -> b 

instance Convert Foo Bar where 
    convert = foo2Bar 

instance Convert Foo Baz where 
    convert = foo2Baz 

instance Convert Bar Baz where 
    convert = bar2Baz 

全射関係:MultiParamTypeClassesで行わ。そして、あなたはすべての含まれているタイプのための新しいデータ型を宣言する必要があるが、そのようなデータを家族とTypeFamiliesを介して行うことができ箱なしの容器、用としてのタイプ、間

class HasLength a b | a -> b where 
    getLength :: a -> b 

instance HasLength [a] Int where 
    getLength = length 

instance HasLength (Set a) Int where 
    getLength = S.size 

instance HasLength Event DateDiff where 
    getLength = dateDiff (start event) (end event) 

全単射関係:MultiParamTypeClassesFunctionalDependenciesで完了例えば、newtypeである。どちらかそれまたは私はGHC 8に先立ってMultiParamTypeClassesFunctionalDependenciesを完了できないと思い単射型家族、持つ:

class Unboxed a b | a -> b, b -> a where 
    toList :: a -> [b] 
    fromList :: [b] -> a 

instance Unboxed FooVector Foo where 
    toList = fooVector2List 
    fromList = list2FooVector 

instance Unboxed BarVector Bar where 
    toList = barVector2List 
    fromList = list2BarVector 

そして最後に、このようなpython2などの2つの種類と第三のタイプ間の全射の関係、またはjavaスタイルの除算関数は、MultiParamTypeClassesも使用してTypeFamiliesで行うことができます。 MultiParamTypeClassesFunctionalDependenciesで行わ:

class Divide a b c | a b -> c where                 
    divide :: a -> b -> c                    

instance Divide Int Int Int where                  
    divide = div 

instance Divide Int Double Double where                
    divide = (/) . fromIntegral                  

instance Divide Double Int Double where                
    divide = (. fromIntegral) . (/)                 

instance Divide Double Double Double where               
    divide = (/) 

私も追加する必要がありますもう一つの事はあなただけのタイプを記述するために持っているように(とにかく上記の例のために)それはFunctionalDependenciesのように思えるとMultiParamTypeClassesもかなりより簡潔であるということです一度、そしてあなたは、あなたがTypeFamiliesで行うようにインスタンスごとに入力する必要がダミーの型名を思い付くする必要はありません:対

instance FooBar LongTypeName LongerTypeName where 
    FooBarResult LongTypeName LongerTypeName = LongestTypeName 
    fooBar = someFunction 

instance FooBar LongTypeName LongerTypeName LongestTypeName where 
    fooBar = someFunction 

そうでなければ私は確信していない限り、実際にはTypeFamiliesと気にしないでください。FunctionalDependenciesMultiParamTypeClassesのみを使用してください。私が言う限りでは、私のコードをより簡潔に、一貫性のあるものにして(気にする拡張が少なくて済む)、オープンなタイプの関係や全単射の関係などの柔軟性を与えてくれるからです(潜在的に後者はGHC 8)。

+1

タイプファミリは一般的に、特に型関数を実装するときに、基底関数よりもはるかに優れています。 – ErikR

+1

また、読みやすさの問題もあります。 FunDepsで表現された '型F a = G(H(G a)(G a)) 'に相当するものはいくつかの補助型変数を含むいくつかの制約を必要とします。私がこのような制約を読んだとき、私は自分自身を機能的な形で表現しようとしていました。おそらく、これは私がプロローグよりも機能的なコードを読むのに慣れているからです。 – chi

+0

@ErikRなぜですか?両方のオプションが実行可能である例は、型の解決に関して全く同じであり、私にとってはそうではないと思われるため、それは信じられないほど奇妙に思えます。 'FunctionalDependencies'や' MultiParamTypeClasses'の現在の実装やそれらの相互作用の欠点ですか?それとももっと根本的なものなのでしょうか?私は実際にはパフォーマンスの理由から、同じものに対しては半分の時間をかけて別の、もっと冗長な構文を使う必要がないのが良いと思うので、前者を期待しています。 – semicolon

答えて

3

と比較してTypeFamiliesが実際に輝いている例は、FunctionalDependenciesです。実際に、私はタイプレベルの置換の問題を(私はQData.hsQuipperに、この特定のバリアントに偶然出会った)考えてみましょう、同等MultiParamClasses解決策を考え出すなど

FlexibleInstancesを使用して一つでも、OverlappingInstanceを、あなたに挑戦します。本質的にあなたがしたいことは、あるタイプを別のタイプに再帰的に置き換えることです。例えば、私はEither [Int] StringBoolの代替Int

    • のことができるようにと Either [Int] StringBoolため Either [Bool] String
    • 代替[Int]を取得し、Either [Int] String[Bool]ためEither Bool String
    • 代替[Int]を取得し、Either [Bool] Stringを取得したいです。

    まったく、タイプレベル置換の通常の考え方が必要です。クローズドタイプのファミリーでは、どのタイプでもこれを行うことができます(より高級なタイプのコンストラクタごとに余分なラインが必要ですが、私は* -> * -> * -> * -> *に止まっていました)。

    {-# LANGUAGE TypeFamilies #-} 
    
    -- Subsitute type `x` for type `y` in type `a` 
    type family Substitute x y a where 
        Substitute x y x = y 
        Substitute x y (k a b c d) = k (Substitute x y a) (Substitute x y b) (Substitute x y c) (Substitute x y d) 
        Substitute x y (k a b c) = k (Substitute x y a) (Substitute x y b) (Substitute x y c) 
        Substitute x y (k a b) = k (Substitute x y a) (Substitute x y b) 
        Substitute x y (k a) = k (Substitute x y a) 
        Substitute x y a = a 
    

    そしてghciにしようと私は、所望の出力を得る:言ったことで

    > :t undefined :: Substitute Int Bool (Either [Int] String) 
    undefined :: Either [Bool] [Char] 
    > :t undefined :: Substitute [Int] Bool (Either [Int] String) 
    undefined :: Either Bool [Char] 
    > :t undefined :: Substitute [Int] [Bool] (Either [Int] String) 
    undefined :: Either [Bool] [Char] 
    

    を、多分あなたはなぜ私はMultiParamClassesなくTypeFamiliesを使用していますあなた自身に尋ねるべきです。上記の例のうち、Convertを除くすべてがタイプファミリに変換されます(type宣言にはインスタンスごとに余分な行が必要です)。

    さらに、Convertについては、このようなことを定義することをお勧めしません。 Convertへの自然な拡張など

    、彼らが書くのはエレガントであるとしてGHC用として解決不能です
    instance (Convert a b, Convert b c) => Convert a c where 
        convert = convert . convert 
    
    instance Convert a a where 
        convert = id 
    

    としてインスタンスだろう...

    明確にするために、私はMultiParamClassesのない用途が存在しないと言っておりません可能であれば、TypeFamiliesを使用する必要があります。これは、関係だけでなくタイプレベルの関数について考えることができます。

    This old HaskellWiki page does an OK job of comparing the two

    EDIT

    は私がblog

    タイプファミリーaugustssからつまずいたいくつかのより多くの対照的なと歴史が 関連する種類の型クラスを持つ必要性から生まれました。後者は、マルチパラメータタイプのクラスでエミュレートされた になる可能性があるため、厳密には必要ではありませんが、多くの場合、表記法がはるかに良い になります。同じことが型ファミリにも当てはまります。彼らは もマルチパラメータ型のクラスでエミュレートすることができます。しかし、MPTCは にタイプの計算を行うロジックプログラミングスタイルを与えています。タイプ ファミリ(これは、 引数でパターンマッチングできる型関数だけです)は、関数型プログラミングと似ています。

    閉じたタイプファミリを使用する は、タイプクラスによっては達成できない余分な強さを追加します。 型クラスから同じパワーを得るには、閉じたタイプ クラスを追加する必要があります。これは非常に便利です。これはインスタンスチェーン があなたに与えるものです。

  • +0

    私は最終的に多かれ少なかれそれを得ると思います。あなたがしているのは、元の投稿のように型の関係を指定していて、特に非標準的な方法でそれらを使用しないと、 'FunctionalDependencies'と' MultiParamTypeClasses'はかなりうまくいくと思います。しかし、それを越えて真のタイプレベルのプログラミングをしようとするなら、あなたは 'TypeFamilies'でずっと良いです。柔軟性と一般的な優雅さの面でしたがって、その優雅さと柔軟性を維持するためには、一般的に 'TypeFamilies'に固執する必要があります。 – semicolon

    +0

    @セミコロンええ、それは良い要約です。 'TypeFamilies'は' MultiParamTypeClasses'より新しいもので、IFRCは型のプログラミングを(論理的な等価物ではなく)値型プログラミングに似ています。 – Alec

    1

    機能依存性は制約解決のプロセスにのみ影響し、タイプファミリは非統語型の等価性の概念を導入し、GHCの中間形式で変換されました。これは、タイプファミリーがGADTとよりよく対話することを意味します。関数の依存関係がここでどのように失敗するかの正規の例については、this questionを参照してください。

    関連する問題