43

余暇にはHaskellを学んでいるので、これは初心者の質問です。

Either a私の読書で、私はどのようにインスタンスが作成されるかを示す例に出くわしましたFunctor

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

ここで、値コンストラクターの場合に実装がマップされる理由を理解しようとしていますが、 ?Rightの場合はマップされません。Left

これが私の理解です:

まず、上記のインスタンスを次のように書き直します

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

今:

  1. そんなこと知ってるfmap :: (c -> d) -> f c -> f d

  2. で代用fするEither afmap :: (c -> d) -> Either a c -> Either a d

  3. のタイプRight (g x)Either a (g x)であり、のタイプg xdであるため、のタイプはでRight (g x)ある必要がありますEither a d。これは、私たちが期待するものですfmap(上記の2を参照)。

  4. ここでLeft (g x)、同じ理由を使用して、そのタイプがEither (g x) b、つまりEither d b、期待するものではないことを示すことができfmapます(上記の2を参照)。dこれは、最初のパラメーターではなく、2番目のパラメーターである必要があります。したがって、にマップすることはできませんLeft

私の推論は正しいですか?

4

4 に答える 4

24

これは正しいです。この動作には、もう 1 つの非常に重要な理由があります。Either a b計算が成功bし、エラー メッセージが返されるか失敗する可能性があると考えることができますa。(これは、モナドインスタンスの仕組みでもあります)。したがって、関数インスタンスが値に触れないのは当然のLeftことです。計算にマップしたいので、失敗した場合、操作するものは何もありません。

于 2011-03-04T14:56:48.390 に答える
14

もちろん、あなたのアカウントは正しいです。おそらく、このようなインスタンスで問題が発生する理由は、一度に無限に多くのファンクターインスタンスを定義しているためです(可能なLeftタイプごとに1つ)。しかし、Functorインスタンスは、システム内の無限に多くのタイプを操作する体系的な方法です。したがって、システム内の無限に多くのタイプを体系的に操作するための無限に多くの方法を定義しています。インスタンスには、2つの方法で一般性が含まれます。

ただし、段階的に見ていくと、それほど奇妙ではないかもしれません。これらのタイプの最初のものはMaybe、ユニットタイプ()とその唯一の正当な値を使用する長いバージョン()です。

data MightBe b     = Nope ()    | Yep b
data UnlessError b = Bad String | Good b
data ElseInt b     = Else Int   | Value b

ここで私たちは疲れて抽象化するかもしれません:

data Unless a b    = Mere a     | Genuine b

ここで、Functorインスタンスを問題なく作成します。最初のインスタンスは、次のインスタンスによく似ていますMaybe

instance Functor MightBe where
  fmap f (Nope ()) = Nope ()   -- compare with Nothing
  fmap f (Yep x)   = Yep (f x) -- compare with Just (f x)

instance Functor UnlessError where
  fmap f (Bad str) = Bad str   -- a more informative Nothing
  fmap f (Good x)  = Good (f x)

instance Functor ElseInt where
  fmap f (Else n) = Else n 
  fmap f (Value b) = Value (f b)

しかし、繰り返しになりますが、なぜわざわざ抽象化してみましょう。

instance Functor (Unless a) where
  fmap f (Mere a) = Mere a
  fmap f (Genuine x) = Genuine (f x)

、および値は変更されていないため、用語は変更されていませんMere a()StringInt

于 2011-03-04T16:20:56.790 に答える
5

他の人が述べたように、Eitherタイプは両方の引数のファンクターです。しかし、Haskellでは、型の最後の引数でファンクターのみを(直接)定義することができます。このような場合、newtypesを使用して制限を回避できます。

newtype FlipEither b a = FlipEither { unFlipEither :: Either a b }

したがって、スワップされた型引数を使用してFlipEither :: Either a b -> FlipEither b aをラップするコンストラクターがあります。そして、それを元に戻すdectructorがあります。これで、の最後の引数でファンクターインスタンスを定義できます。これは、実際にはの最初の引数です。EithernewtypeunFlipEither :: FlipEither b a -> Either a bFlipEitherEither

instance Functor (FlipEither b) where
    fmap f (FlipEither (Left x))  = FlipEither (Left (f x))
    fmap f (FlipEither (Right x)) = FlipEither (Right x)

FlipEitherしばらく忘れると、 /がスワップされただけで、 Functorforの定義だけが得られることに注意してください。そして今、の最初の型引数にインスタンスが必要なときはいつでも、値をラップして、後でアンラップすることができます。例えば:EitherLeftRightFunctorEitherFlipEither

fmapE2 :: (a -> b) -> Either a c -> Either b c
fmapE2 f = unFlipEither . fmap f . FlipEither

更新:Data.Bifunctorを見てください。その中Either(,)はのインスタンスです。各バイファンクターには2つの引数があり、それぞれのファンクターです。Bifunctorこれはのメソッドfirstとに反映されsecondます。

の定義BifunctorEither非常に対称的です。

instance Bifunctor Either where
    bimap f _ (Left a)  = Left (f a)
    bimap _ g (Right b) = Right (g b)

    first  f = bimap f id

    second f = bimap id f
于 2012-11-11T07:41:28.673 に答える
1

ここで、Right 値コンストラクターの場合は実装がマップされるのに、Left 値コンストラクターの場合はマップされない理由を理解しようとしています。

ここにプラグインすると、理にかなっているかもしれません。

Assume a = String (エラー メッセージ) Float に Each a を適用します。

したがって、f: Float -> Integer があり、たとえば丸めます。

(いずれかの文字列) (浮動小数点) = 両方の文字列浮動小数点数。

now (fmap f):: どちらかの文字列 Float -> どちらかの文字列 Int では、f で何をするつもりですか? f は文字列をどうするか分からないので、何もできません。それは明らかに、左の値を変更せずに、右の値だけを操作できるということです。

言い換えれば、次のように与えられる明らかな fmap があるため、いずれか a がファンクターです。

  • 右の値に f を適用
  • 左の値は何もしません
于 2011-03-04T18:43:51.053 に答える