Понимание того, как Either является экземпляром Functor - PullRequest
36 голосов
/ 04 марта 2011

В свободное время я изучаю 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 a, мы получим fmap :: (c -> d) -> Either a c -> Either a d

  3. тип Right (g x) равен Either a (g x), а тип g x равен d, поэтому мы имеем, что тип Right (g x) равен Either a d, что мы и ожидаем от fmap (см. 2. выше)

  4. Теперь, если мы посмотрим на Left (g x), мы можем использовать то же рассуждение, чтобы сказать, что его тип Either (g x) b, то есть Either d b, что не то, что мы ожидаем от fmap (см. 2 выше): d должен быть вторым параметром, а не первым! Так что мы не можем нанести на карту Left.

Верны ли мои рассуждения?

Ответы [ 4 ]

21 голосов
/ 04 марта 2011

Это правильно.Существует также еще одна довольно важная причина такого поведения: вы можете думать о Either a b как о вычислении, которое может завершиться успешно и вернуть b или завершиться ошибкой с сообщением об ошибке a.(Это также, как работает экземпляр монады).Поэтому вполне естественно, что экземпляр functor не будет касаться значений Left, так как вы хотите отобразить вычисления, если они не пройдут, манипулировать нечего.

12 голосов
/ 04 марта 2011

Ваш аккаунт, конечно, прав. Возможно, причина, по которой у нас возникают трудности с такими экземплярами, заключается в том, что мы действительно определяем бесконечно много экземпляров функторов одновременно - по одному для каждого возможного типа Left. Но экземпляр Functor - это систематический способ работы с бесконечным числом типов в системе. Таким образом, мы определяем бесконечно много способов систематической работы с бесконечно большим количеством типов в системе. Экземпляр включает в себя общность двумя способами.

Если взять это поэтапно, возможно, это не так странно. Первый из этих типов - это длинная версия 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 не затрагиваются, поскольку значения (), String и Int не затрагиваются.

4 голосов
/ 11 ноября 2012

Как уже упоминалось, тип Either является функтором в обоих аргументах. Но в Haskell мы можем (напрямую) определять только функторы в последних аргументах типа. В подобных случаях мы можем обойти ограничение, используя newtype s:

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

Итак, у нас есть конструктор FlipEither :: Either a b -> FlipEither b a, который упаковывает Either в наш newtype с аргументами типа свопинга. И у нас есть dectructor unFlipEither :: FlipEither b a -> Either a b, который разворачивает его обратно. Теперь мы можем определить экземпляр функтора в последнем аргументе FlipEither, который фактически является первым аргументом Either:

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

Обратите внимание, что если мы на время забудем FlipEither, мы получим только определение Functor для Either, просто с Left / Right подкачкой. И теперь, когда нам нужен экземпляр Functor в аргументе первого типа Either, мы можем заключить значение в FlipEither и развернуть его позже. Например:

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

Обновление: Посмотрите на Data.Bifunctor , из которых Either и (,) являются экземплярами. Каждый bifunctor имеет два аргумента и является функтором в каждом из них. Это отражено в методах Bifunctor first и second.

Определение Bifunctor из Either очень симметрично:

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
1 голос
/ 04 марта 2011

Теперь я пытаюсь понять, почему реализация отображается в случае конструктора значения Right, а не в случае Left?

Подключите здесьи это может иметь смысл.

Предположим, что a = String (сообщение об ошибке). Вы применяете Either a к Float.

Итак, у вас есть f: Float -> Integer, например, округление.

(Любая строка) (Плавающая) = Плавающая строка.

сейчас (fmap f) :: Either String Float -> Either String Int Итак, что вы собираетесь делать с f?f не имеет ни малейшего понятия, что делать со строками, поэтому вы ничего не можете там сделать.То есть очевидно единственное, на что вы можете воздействовать - это правильные значения, оставляя левые значения без изменений.

Другими словами, либо a является функтором, потому что существует такой очевидный fmap, заданный как:

  • для значений Right применяется f
  • для значений Left ничего не делают
...