ファンクタークラスは次のとおりです。
class Functor f where
fmap :: (a -> b) -> f a -> f b
「f」自体は、fmap行の型変数に適用されるため、型コンストラクターであることに注意してください。これを明確にするためのいくつかの例を次に示します。
型構築子:
IO
Maybe
Either String
タイプ:
IO Char
Maybe a
Either String String
「たぶんa」は、1つの型構築子(「たぶん」)と1つの型変数(「a」)を持つ型です。まだ具体的なものではありませんが、ポリモーフィック関数の型アノテーションに使用できます。
「Either」は2つの型引数をとる型コンストラクターであるため、1つを適用した後でも(たとえばEither String
、別の型引数をとることができるため、型コンストラクターのままです)。
Functor
これのポイントは、インスタンスを定義するとき、型コンストラクターf
は変更できないということです。f
これは、の引数と結果の両方 と同じ変数、で表されるためですfmap
。変更が許可されているタイプは、f
コンストラクターに適用されるタイプのみです。
あなたが書くときinstance Functor (Either c)
、は、の宣言の至る所にEither c
記入されます。これにより、fmapはこのインスタンスに対して次のタイプになります。f
fmap
fmap :: (a -> b) -> (Either c) a -> (Either c) b
の定義ではEither
、この型を取得する唯一の便利な方法は Right
、関数に値を適用することです。「Either」には、タイプが異なる可能性のある2つの値があることに注意してください。ここで、Left
値のタイプは「c」であるため、関数に適用することはできません(「a」が必要です)[1]。また、が残っているため、結果は正しくEither b a
ありません。クラス定義と一致しません。
「f」を「Eitherc」に置き換えて、fmapの上記の型シグネチャを「Eitherc」インスタンスで取得した後、実装を記述します。考慮すべき2つのケース、左と右があります。型署名は、左側の型「c」は変更できないことを示しています。また、実際のタイプがわからないため、値を変更する方法もありません。私たちにできることはそれを放っておくことだけです:
fmap f (Left rval) = Left rval
右側の場合、型アノテーションは、タイプ「a」の値からタイプ「b」の値に変更する必要があることを示しています。最初の引数はそれを正確に実行する関数であるため、入力値を持つ関数を使用して新しい出力を取得します。2つを組み合わせると、完全な定義が得られます
instance Functor (Either c) where
fmap f (Right rval) = Right (f rval)
fmap f (Left lval) = Left lval
ここでは、より一般的な原則が機能しています。そのため、少なくともプレリュードの定義では、左側を調整するFunctorインスタンスを作成することは不可能です。上からいくつかのコードをコピーする:
class Functor f where
fmap :: (a -> b) -> f a -> f b
instance Functor (Either c) where ...
インスタンス定義に型変数「c」がありますが、クラス定義に記載されていないため、どのクラスメソッドでも使用できません。だからあなたは書くことができません
leftMap :: (c -> d) -> Either c a -> Either d a
leftMap mapfunc (Left x) = Left (mapfunc x)
leftMap mapfunc (Right x) = Right x
instance Functor (Either c) where
--fmap :: (c -> d) -> Either c a -> Either d a
fmap = leftMap
leftMap、つまりfmapの結果は次のようになります(Either d) a
。は(Either c)
に変更されました(Either d)
が、Functorクラスで表現する方法がないため、これは許可されていません。これを表現するには、2つの型変数を持つクラスが必要です。
class BiFunctor f where
lMap :: (a -> b) -> f a c -> f b c
rMap :: (c -> d) -> f a c -> f a d
biMap :: (a -> b) -> (c -> d) -> f a c -> f b d
このクラスでは、左型変数と右型変数の両方がスコープ内にあるため、どちらか(または両方)で動作するメソッドを作成できます。
instance BiFunctor Either where
lMap = leftMap
rMap = rightMap --the same as the standard fmap definition
biMap fl fr e = rMap fr (lMap fl e)
実際には、人々は通常、BiFunctorクラスに「biMap」を記述し、左または右のマッピングが必要な場合は他の関数に「id」を使用します。
[1]より正確には、Left値のタイプは「c」であり、関数は「a」を予期しますが、「c」タイプはクラス定義のスコープ内にないため、タイプチェッカーはこれらのタイプを統合できません。