Haskell Language
タイプファミリー
サーチ…
タイプシノニムファミリー
型シノニムファミリーは単なる型レベル関数です。パラメータ型と結果型を関連付けます。これらは3つの異なる品種があります。
クローズド・タイプ・シノニム・ファミリ
これらは、通常の値レベルのHaskell関数とよく似ています。特定の型を他の型にマッピングするいくつかの句を指定します。
{-# LANGUAGE TypeFamilies #-}
type family Vanquisher a where
Vanquisher Rock = Paper
Vanquisher Paper = Scissors
Vanquisher Scissors = Rock
data Rock=Rock; data Paper=Paper; data Scissors=Scissors
タイプ・シノニム・ファミリーを開く
これらは、型クラスインスタンスによく似ています。誰でも他のモジュールに節を追加できます。
type family DoubledSize w
type instance DoubledSize Word16 = Word32
type instance DoubledSize Word32 = Word64
-- Other instances might appear in other modules, but two instances cannot overlap
-- in a way that would produce different results.
クラス関連型の同義語
オープンタイプのファミリーは、実際のクラスと組み合わせることもできます。これは通常、 関連するデータファミリと同様に、いくつかのクラスメソッドは追加のヘルパーオブジェクトを必要とし、これらのヘルパーオブジェクトはインスタンスごとに異なる可能性がありますが、共有することもできます。良い例はVectorSpace
クラスです:
class VectorSpace v where
type Scalar v :: *
(*^) :: Scalar v -> v -> v
instance VectorSpace Double where
type Scalar Double = Double
μ *^ n = μ * n
instance VectorSpace (Double,Double) where
type Scalar (Double,Double) = Double
μ *^ (n,m) = (μ*n, μ*m)
instance VectorSpace (Complex Double) where
type Scalar (Complex Double) = Complex Double
μ *^ n = μ*n
最初の2つのインスタンスでは、 Scalar
の実装はどのように同じであるかに注意してください。これは、関連するデータファミリでは不可能です。データファミリは注入型であり、型同義語ファミリはそうではありません。
非注入性は上記のような可能性を開くが、タイプ推論をより困難にする。たとえば、次のようにタイプチェックは行われません。
class Foo a where
type Bar a :: *
bar :: a -> Bar a
instance Foo Int where
type Bar Int = String
bar = show
instance Foo Double where
type Bar Double = Bool
bar = (>0)
main = putStrLn (bar 1)
この場合、 bar
の引数自体は単なる多形Num
リテラルなので、コンパイラはどのインスタンスを使用するかを知ることができません。また、型関数Bar
は、逆方向には解決できません。なぜなら、それは注入†ではなく、したがって可逆ではないからです( Bar a = String
持つ型が複数ある可能性があります)。
† これらの2つのインスタンスだけでは、実際にはインジェクティブですが、コンパイラは後でインスタンスを追加して動作を中断することは誰にも分かりません。
データ型ファミリ
データ・ファミリを使用すると、型引数に基づいて異なる実装を持つデータ型を作成できます。
スタンドアロンデータファミリ
{-# LANGUAGE TypeFamilies #-}
data family List a
data instance List Char = Nil | Cons Char (List Char)
data instance List () = UnitList Int
上記の宣言では、 Nil :: List Char
、 UnitList :: Int -> List ()
関連するデータファミリ
データファミリは、タイプメスに関連付けることもできます。これは、ジェネリックなtypeclassメソッドには必要ですが、具体的なインスタンスに応じて異なる情報を含める必要がある「ヘルパーオブジェクト」を持つタイプにはしばしば役に立ちます。たとえば、リスト内の索引付け場所には単一の番号が必要ですが、ツリーでは各ノードのパスを示す番号が必要です。
class Container f where
data Location f
get :: Location f -> f a -> Maybe a
instance Container [] where
data Location [] = ListLoc Int
get (ListLoc i) xs
| i < length xs = Just $ xs!!i
| otherwise = Nothing
instance Container Tree where
data Location Tree = ThisNode | NodePath Int (Location Tree)
get ThisNode (Node x _) = Just x
get (NodePath i path) (Node _ sfo) = get path =<< get i sfo
インジェクション
タイプファミリは必ずしも注入型ではありません。したがって、アプリケーションからパラメータを推論することはできません。例えば、ではservant
、型指定されたServer a
私たちは型推論することはできませんa
。この問題を解決するために、 Proxy
を使用することができます。たとえば、 servant
では、 serve
関数の型は... Proxy a -> Server a -> ...
。私たちは推測することができますからa
Proxy a
のでProxy
で定義されdata
単射です。