31

Learn YouaHaskellにはファンクターについての例があります。私はLYAHとテキストを読んで、何が起こるかを理解することができます-しかし、私はこのようなものを書くのに十分なことを知りません。私はこの問題をHaskellでよく見かけます。

instance Functor (Either a) where  
    fmap f (Right x) = Right (f x)  
    fmap f (Left x) = Left x

しかし、私は混乱しています..なぜこれは完了しないのですか

instance Functor (Either a) where  
    fmap f (Right x) = Right (x)  
    fmap f (Left x) = Left (f x)

が最上位の定義で使用されていない場合f、他に何が制約されxて満たすことができないかLeft

4

10 に答える 10

42

ファンクタークラスは次のとおりです。

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はこのインスタンスに対して次のタイプになります。ffmap

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」タイプはクラス定義のスコープ内にないため、タイプチェッカーはこれらのタイプを統合できません。

于 2010-07-02T10:06:52.540 に答える
9

左と右はタイプではなくLeft xRight y同じタイプです。それらはEitherの単なるコンストラクターです。あなたは考えるかもしれません

Left :: c -> Either c d
Right :: d -> Either c d

fmap左と右は異なる値であることがわかっているため、2つの宣言を持つことができます。まるで

g :: Int -> Int
g 1 = 2
g 2 = 4
g n = n

ここでは、1と2を言うことはできずn、パターンマッチングが機能するという理由だけで、異なる「タイプ」になります。


Functorクラスは、次のように定義されます。

class Functor f where
  fmap :: (a -> b) -> f a -> f b

abは任意のタイプであることに注意してください。わかりやすくするためaに、インスタンスの名前をに変更しc、関数の名前をに変更しましょffunc

instance Functor (Either c) where  
    fmap func (Right x) = Right (x)  
    fmap func (Left x) = Left (func x)

どちらかがデフォルトの定義に従っていると仮定します

data Either c d = Left c | Right d

次に、あなたの定義により、

fmap    func     (Right x) = Right x
-- # (a -> b) ->      f a       f  b
-- # f = Either c

これは力a = b、そして

fmap    func     (Left x) = Left (func x)
-- # (a -> b) ->     f a       f b
-- # f = Either c

c = a = b。どちらもを考慮すると無効abありc、独立した任意のタイプです。

于 2010-07-01T06:36:34.857 に答える
8

さて、これをもう1つ簡単に試してみましょう。

なぜこれがコンパイルされないのかとあなたは尋ねます:

instance Functor (Either a) where
    fmap f (Right x) = Right (x)
    fmap f (Left x) = Left (f x)

それでは、クラスインスタンス宣言の一部として配置せずに同じ関数を定義しようとすることで、問題を単純化してみましょう。

それは私たちに与えます

foo f (Right x) = Right (x)
foo f (Left x) = Left (f x)

これは確かにコンパイルされます。ghciは型シグネチャを教えてくれます:

*Main> :t foo
foo :: (t1 -> a) -> Either t1 t -> Either a t

いくつかの変数の名前を変更して、より均一に見えるようにします。

foo :: (a -> b) -> Either a c -> Either b c

それは完全に理にかなっています。関数を取り、どちらかの左側に適用します。

しかし、fmapの署名は何ですか?

*Main> :t fmap
fmap :: (Functor f) => (a -> b) -> f a -> f b

それでは、署名Either cのfを置き換えましょう( 2つの異なるsが混同されないように名前を変更しました)。fmapEither aEither ca

fmap :: (a -> b) -> Either c a -> Either c b

問題がわかりますか?Either a あなたの関数は完全に有効です-それはfmapが必然的に持っていなければならないものとは異なるタイプを持っているだけです。

これは、型についての一種の美しいことです。の署名が与えられた場合、どちらかでのfmap意味のある実装は実際には1つだけです。fmap

運が良ければ、注意深く、同じような状況になることがあります。型アノテーションが与えられると、関数はほとんどそれ自体を書き込みます。

編集:以下の質問に答えようとしています。

1)「2つの機能の構成」は行われていません。型アノテーションを取得するには、関数シグネチャを確認fmapし、表示されるすべての場所を。に置き換えます。これをfmapの型アノテーションの「特殊化」と呼びます。つまり、通常のタイプのfmapよりも厳密には一般的ではありません。より特殊なタイプの関数が必要な場合は、一般的なタイプのものを問題なく渡すことができます。Either afmapfEither a

2)左側にマッピングするための関数(上記の例では「foo」と名付けました)は問題ありません。それはうまく機能し、あなたが望むことをします。fmap名前を付けてFunctorインスタンスで使用することはできません。onLeft個人的には、またはのような名前を付けmapLeftます。

以下のすべては無視することができます/情報のためですが、近い将来/実際の使用で将来読むための提案ではありません:

非常に技術的になりたい場合は、左側と右側の両方にマップできるため(ただし、後者の場合はFunctorのみを宣言できます)、どちらもFunctorであるだけでなく、Bifunctorでもあります。これは、たとえば、ekmettのCategory-Extrasライブラリで提供されます(http://hackage.haskell.org/packages/archive/category-extras/0.44.4/doc/html/Control-Bifunctor.htmlを参照)。

プログラムを使った計算や、バイファンクターをより厳密に使用する「折り紙プログラミング」など、すばらしいものがたくさんあります。あなたはここでそれについて読むことができます:http://lambda-the-ultimate.org/node/1360。しかし、少なくともHaskellにもっと精通するまでは、おそらくそうしたくないでしょう。それはコンピューター能力、マシー、研究的、そして非常にクールですが、慣用的なHaskellプログラミングを理解するためにまったく必要ではありません。

于 2010-07-01T17:13:44.417 に答える
3

(編集して質問にもっとよく答えようとします)

どちらかの定義は次のとおりです。

data Either a b = Left a | Right b

したがって、「Either」は2つの型引数を取ります。ちなみに、技術的には「どちらか」は実際には型ではなく型コンストラクターです。型を作成するには型引数が必要です。

ファンクターの定義は次のとおりです。

class Functor f where
   fmap :: (p -> q) -> f p -> f q

したがって、このクラス定義では、Functorのインスタンスであるタイプ「f」はタイプ引数を取る必要があります。これは宣言されていません。「fp」と「fq」から推測されます。ここで「f」には型パラメーターが与えられているので、それは1つを取る型でなければなりません。

(注:元の定義では、「p」と「q」の代わりに「a」と「b」が使用されていました。後で説明するときに、「どちらかa b」と区別するために、異なる文字を使用しています)

ほとんどの場合、「f」はリストやツリーのようなコンテナタイプです。たとえば、あなたは

data Tree a = ...

instance Functor Tree where
   fmap func_a2b tree_of_a = ... --  tree of b.

ただし、「Either」は2つのタイプのパラメーターを受け取るので、このスキームにどのように適合させることができますか?答えは、型は関数と同じように部分適用を持つことができるということです。関数を宣言できるのと同じように

foo x y = ...

次に、2番目の引数を期待する新しい関数を取得するために「foo2」と言うので、2番目の型引数を期待する新しい型を取得するために「Eithera」と言うことができます。

次に、元のインスタンスを見てください。

instance Functor (Either a) where ....

したがって、ここで「Either a」は、Functorがそのインスタンスに期待するのと同じように、もう1つの引数を期待する型コンストラクターです。したがって、「Eithera」の「fmap」のタイプは次のようになります。

fmap :: (p -> q) -> Either a p -> Either a q

したがって、「where」句で、このタイプの「fmap」の定義を指定する必要があります。2番目の型パラメーターは「Right」コンストラクターに使用され、関数が適用されるコンストラクターであるため、最初に引用するものはこの型になります。2つ目は、次のタイプになるため、機能しません。

fmap :: (p -> q) -> Either p a -> Either q a

そして、それはFunctorクラスがそうなると言っていることではありません。

于 2010-07-01T14:59:40.467 に答える
3

最終的にはあなたのフォーマットに劈開しますが、説明がより明確になると思うので、少し異なるフォーマットの何かから始めます。

別のデータ型を考えてみましょう

data Choice a = Default Integer | Chosen a

-- This corresponds to your top, working, instance.
instance Functor Choice where
  fmap f (Default i) = Default i
  fmap f (Chosen  a) = Chosen  (f a)

このインスタンスが機能する理由は明らかです。ただし、次についてはどうでしょうか。

-- Broken!
instance Functor Choice where
  fmap f (Default i) = Default (f i)
  fmap f (Chosen  a) = Chosen  a

これが機能しない理由がわかるはずです。のタイプfmapFunctor f => (a -> b) -> f a -> f b; この文脈では、それは(a -> b) -> Choice a -> Choice bです。したがって、f引数の型はa -> b。です。ただし、2番目の(失敗した)インスタンス宣言では、を記述しf iます。データ型宣言があるため、これはであるi必要があることがわかっているためInteger、適用できませんf。同様に、aはタイプaChosen a持っているので、タイプChosen aではなくタイプを持ちChosen bます。したがって、Functor下部のインスタンスは機能しません。

さて、例のように、それはタイプに従うEitherので、あなたのトップインスタンスは機能します。Choiceいくつかの名前を変更して、それを見てみましょう。

instance Functor (Either c) where  
  fmap f (Left  c) = Left  c
  fmap f (Right a) = Right (f a)

Functorこのインスタンス宣言はforのインスタンスを宣言しませんEither—それはできません。のインスタンスであるものは、Functor1つの型パラメーターを取る必要があります。したがって、タイプパラメータをInt受け取らないため、ファンクタになることはできませんが、完全なタイプ であるため、ファンクタになることができます。ただし、次の2つのタイプパラメータを取ります。したがって、このインスタンスが行うことは、それがあらゆる可能なのファンクターであることを宣言することです。これは、インスタンス宣言の期間中は修正されます。それでは、タイプを調べて追加しましょう(これは正当な構文ではありません!):Int[]Maybe[a]Maybe aEitherEither a bEither ccc

instance Functor (Either c) where
  fmap :: forall a b. (a -> b) -> (Either c) a -> (Either c) b
  fmap f (Left  (c :: c)) = Left  c
  fmap f (Right (a :: a)) = Right (f a :: b)

fはタイプa -> bがありますが、cのタイプはに固定されているため、;とc書くことはできません。Left (f c)できたとしても、をcそのままにして、を返すことができるようにし(Either c) bます。同様に、タイプの何かを取得するには、に適用する必要があります。fab

これが、ボトムインスタンスが機能しない理由でもあります。固定型にのみ適用されるすべての型に対して機能する必要がある関数があり、変換する必要cのある型はそのままにしておきます。型署名を追加して、もう一度見てみましょう。

instance Functor (Either c) where  
  fmap :: forall a b. (a -> b) -> (Either c) a -> (Either c) b
  fmap f (Left  (c :: c)) = Left  (f c)
  fmap f (Right (a :: a)) = Right a

f :: a -> bここで、関数定義の最初の部分は、機能できない固定タイプの関数を適用しようとしているcため、これはすでに失敗しています。しかし、これがどのタイプを生成するかを見てみましょう。この場合、(どういうわけか)f cタイプがであり、タイプがbであるとa予想されますa。その場合、タイプの値を返しますがEither b a、これはまだ許可されていません。

基本的に、問題はこれに起因します。まず、f2つの関数定義句の間で同じであるため、行間で変更できないことに注意してください。次に、を修正し、cそのcインスタンスを宣言していることに注意してください。これはどの場合にも当てはまりますcが、一度に1つしか調べません。最後に、このため、Leftの引数は、期待する型によってパラメーター化されません。fいくつかの固定タイプがあることが保証されていcます。これは、(a)適用できないfこと、および(b)引数に適用する必要Rightがあることを意味します。そうしないと、変更する予定のタイプを変更できないためです。

于 2010-07-01T18:27:52.890 に答える
3

信じられないかもしれませんが、これは魔法ではありません。それはすべて、タイプEither a bがと同じタイプではないことと関係がありEither b aます。これがプレリュードからのどちらかの定義です

data  Either a b  =  Left a | Right b

...型変数aが最初に来て、次にbが来ることに注意してください。また、EitherFunctorの宣言にaのみが含まれていることに注意してください。

instance Functor (Either a) where  
    fmap f (Right x) = Right (f x)  
    fmap f (Left x) = Left x

では、MaybeFunctorの定義を見てみましょう。

instance Functor Maybe where
    fmap = map

ここでは型変数はありませんが、Maybe1つの型パラメーター(のようなMaybe Int)を取ります。私が得ようとしているのは、型はファンクターではなく、型コンストラクターはファンクターであるということです(ファンクターには種類があります*->*)。

したがってf :: b -> c、動作するどちらかのファンクターのバージョンでは、xfrom(Left x)はタイプaであり、これは(Either a)ファンクターであるxため問題ありません。inは(Right x)タイプであるbため、(Right x)タイプ((Either a) b)であり(Right (f x))、タイプ((Either a) c)であるため、fmapはタイプです(b->c) -> ((Either a) b) -> ((Either a) c)。要求に応じ。

失敗したバージョンでは、xin(Right (x))はタイプaではなくb、タイプであるため、 fmapのタイプに適合しないタイプではありません。(Right (x))((Either a) c)

要約すると、機能するfmapが出てきます(b -> c) -> (Either a) b -> (Either a) cが、機能しないfmapが出てきます。これ(b -> c) -> (Either b) a -> (Either c) aは、fmapに適したタイプではありません。

于 2010-07-03T01:07:19.550 に答える
2

トップは機能しますがfmap::(b -> c) -> Either a b -> Either a c、あなたの-ボトム-は必要になるため機能しませんfmap::(a -> c) -> Either a b -> Either a c。ただし、Eitherを次のように変更すると機能します

data Either' a b = Left' b | Right' a deriving (Eq, Show)

instance Functor (Either' a) where  
    fmap f (Right' x) = Right' (x)  
    fmap f (Left' x) = Left' (f x)
于 2010-07-02T11:37:16.913 に答える
2

うまくいけば、これは役立つでしょう...

最初に、しかし、いくつかの背景:

1)Functorには、「型コンストラクター」が必要です。これは、Java /のジェネリックのように、「完全型」を形成するために与えられた別の通常の型を「必要とする」型です。 C++。したがって、たとえば、Listファンクター(ちなみに)、またはArray、(とりわけ)完全なタイプは単なるListではなく、List<A>です。つまり、:Functorは、不完全な型である「型コンストラクター」を取ります。

2)Eitherは、Haskellの人々(Edward Kmett、および他の数学に恵まれたオールスター)がbifunctorと呼ぶコンストラクター型です。完了するには、2つのタイプを指定する必要があります。たとえば、Eitherを完全に使用すると、次のようになります。Either Integer Stringつまり、(ええ、ええ、 "duh!")は、(Left)整数または(Right)文字列のいずれかです。つまり、これは、 「b」が何であるかを決定するときにaまたはaのEither Integerいずれかである不完全なタイプであることを意味します。Left IntegerRight...b

さて、楽しい部分です!

上位のものfmapは、いくつかの型コンストラクターを使用し、それを関数と一緒に使用して、から同様の関数a -> bを作成するために機能します-Haskellでのこれの手に負えないお気に入りの例は、リストの例、別名マップであり、Functorはその一部です。ここで、(先に進んで以前から使用してみましょう)のようなものを使用すると、fmapの型アノテーションは次のようになります。f af b:: (a -> b) -> ([a] -> [b])[ ]Either aEither Integer

fmap :: (a -> b) -> (Either Integer a -> Either Integer b)

(上部の)2つの例は、 -typed値Either Integer aを取得するために、fmapがその型の代表値をどのように処理するかを示しています。Either Integer b

さて、あなたの-bottom-は機能しません。理由は次のとおりです。

  1. sをfs にする関数があります。ab
  2. タイプLeftは整数型であり、整数のままである必要があります(または、Float型であり、Float型のままである必要があります。使用しているタイプの、2つのタイプの左側のタイプはどれでも)。Either
  3. タイプRightは、関数が取るタイプ( " a")である必要があり、関数が作成するタイプ( " b")に移動します。

それはこれをしなければなりません(しかしあなたのものはそうではありません-それがそれが機能しない理由です)、なぜならそれはfmap必要なタイプだからです。具体的には、次の方程式があります。

fmap f (Right x) = Right (x)  
fmap f (Left x) = Left (f x)

あなたの方程式fmapはタイプを与えます:

fmap :: (a -> b) -> Either c d -> Either c d
fmap :: (a -> b) -> Either a d -> Either b d

それは欲しいものに合わないだけでなくfmap、お互いに一貫性さえありません!

半分の本を書いてすみませんが、それがあなたにいくらかの洞察を与えることを願っています。

于 2010-07-01T16:35:42.873 に答える
1

あなたが書き込もうとしているインスタンスはfmap2、今のところそれを呼びましょう、次のタイプを持っています:

fmap2 :: (a -> b) -> Either a c -> Either b c

LANGUAGEプラグマを設定するTypeOperatorsと、GHCはタイプも受け入れます

fmap2 :: (a -> b) -> (a `Either` c) -> (b `Either` c)

理想的な世界では、これも機能します。

fmap2 :: (a -> b) -> (`Either` c) a -> (`Either` c) b

これはFunctorインスタンスを提供します(`Either` c)が、正規演算子(およびそれらのセクション)と型演算子の類似性はこの時点で崩壊します(GHCオプションが欠落している場合を除く)。

つまり、ファンクターの理解は問題ありませんが、型レベルのラムダがないことに悩まされています。

于 2010-07-01T08:26:49.943 に答える
1

えーと…「種類」について一言?..
わかりやすくなるかもしれませんね。
カリー化とは何かを覚えておいてください。つまり、ghciで:

プレリュード>fxyz= x + y*zとします
f ::(数値a)=> a-> a-> a-> a
プレリュード>:tf 1
f 1 ::(Num t)=> t-> t-> t
プレリュード>:tf 1 2
f 1 2 ::(Num t)=> t-> t
プレリュード>:tf 1 2 3
f 1 2 3 ::(Num t)=> t

あなたがタイプで持っているのと同じもの。あなたEitherがそのタイプの種類であると言うとき* -> * -> *(すなわち、それは2つのタイプを取り、タイプを生成します)、そしてあなたがEither a種類であると言うとき、そしてそれの* -> *ために(ところで 、私が覚えているように、種類である必要があります)。したがって、タイプとは、まだ不完全なタイプを意味します(バインドするには「引数」が必要です)。したがって、。で置き換えるとになります。Either a b*Monad aFunctor aa* -> *Either afmap :: (a -> b) -> f a -> f bfmap :: (a -> b) -> (Either c) a -> (Either c) bfEither c

于 2010-07-01T20:00:25.637 に答える