Понимание того, как каждый из них является экземпляром Functor
В свободное время я изучаю 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
Сейчас:
-
Я знаю, что fmap :: (c -> d) -> f c -> f d
-
если подставить f
с помощью Either a
, получим fmap :: (c -> d) -> Either a c -> Either a d
-
тип Right (g x)
равен Either a (g x)
, а тип g x
равен d
, поэтому мы имеем тип Right (g x)
Either a d
, что мы ожидаем от fmap
(см. выше)
-
теперь, если мы посмотрим на Left (g x)
, мы можем использовать те же рассуждения, что сказать, что его тип Either (g x) b
, то есть Either d b
, чего мы не ожидаем от fmap
(см. 2 выше): d
должен быть вторым параметром, а не первым! Таким образом, мы не можем сопоставлять Left
.
Правильны ли мои рассуждения?
Ответы
Ответ 1
Это правильно. Существует еще одна довольно важная причина такого поведения: вы можете думать о Either a b
как о вычислении, которое может преуспеть и вернуть b
или сбой с сообщением об ошибке a
. (Это также, как работает экземпляр монады). Поэтому естественным является то, что экземпляр functor не будет касаться значений Left
, так как вы хотите сопоставить вычисление, если оно терпит неудачу, нечего манипулировать.
Ответ 2
Ваша учетная запись права, конечно. Возможно, причина, по которой мы сталкиваемся с такими случаями, состоит в том, что мы действительно определяем бесконечно много экземпляров-исполнителей сразу - по одному для каждого возможного типа Left
. Но экземпляр Functor - это систематический способ работы с бесконечным числом типов в системе. Поэтому мы определяем бесконечно много способов систематического управления бесконечным числом типов в системе. Экземпляр включает в себя общность двумя способами.
Если взять его поэтапно, возможно, это не так странно. Первым из этих типов является longwinded версия 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
не были затронуты.
Ответ 3
Как уже упоминалось, тип 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
, который разворачивает его обратно. Теперь мы можем определить экземпляр functor в 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
of 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
Ответ 4
Теперь я пытаюсь понять, почему карты реализации в случае Конструктор правого значения, но не в случае левого?
Подключите сюда, и это может иметь смысл.
Предположим, что a = String (сообщение об ошибке)
Вы применяете либо a, либо float.
Итак, у вас есть f: Float → Integer, например, округление.
(Либо String) (Float) = Либо String Float.
now (fmap f):: Либо String Float → Либо String Int
Итак, что вы собираетесь делать с f? f не имеет понятия, что делать со строками, чтобы вы ничего не могли сделать. Это , очевидно,, единственное, на что вы можете воздействовать, - это правильные значения, оставляя неизменным оставленные значения.
Другими словами, либо a является функтором, потому что существует такая очевидная fmap, заданная следующим образом:
- для правых значений применяется f
- для значений Left ничего не делает