2011-03-04 3 views
31

В свободное время я изучаю Haskell, так что это вопрос начинающих.Понимание того, как Либо является экземпляром Functor

В моих показаниях я наткнулся на пример, иллюстрирующий, как 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.

Является ли мое рассуждение правильным?

+11

Или, возможно, более очевидно бифунктор чем функтор - это бифунктора имеет операцию bimap - bimap :: (а -> т) -> (b -> n) -> fab -> fm n. Это дает вам сопоставление как с левыми, так и с правыми. В стандартной библиотеке Haskell нет класса Bifunctor, это связано с тем, что существует намного меньше Bifunctors, чем Functors «в дикой природе», хотя это удобно для Либо и пары (a, b). –

+1

В 3, вы используете (g x) как часть типа, но это значение. Похоже, вы хотели написать 'typeof (g x)' there, а не '(g x)'. – Peaker

+0

@stephen tetley: это интересно! Спасибо – MarcoS

ответ

20

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

+0

Я еще не узнал о монадах :) Я читал, что «Либо ab' используется так, как вы описываете, и что' a' представляет сообщение об ошибке: но это просто соглашение, не так ли? – MarcoS

+0

@MarcoS: Правильно, это только конвенция. Любой из них может быть истолкован как дизъюнкция: значение типа. Или b содержит значение типа a или значение типа b, но не оба. –

+0

В отдельном случае - «Либо строковая строка» «Любой символ строки», «Либо String Int» и т. Д. - это так, конечно; нет никакой разницы между правым и левым типами. Но в качестве функтора вы можете fmap over - как (или String), в этих примерах - полная асимметрия. Тип, который входит в конкретный экземпляр Functor (здесь 'String'), должен представлять что-то общее, которое можно найти во всех обычных вычислениях с любым типом (что происходит справа, через' fmap') - поэтому ошибка «толкование, хотя и вряд ли единственное, не случайно. – applicative

8

Ваша учетная запись права, конечно. Возможно, причина, по которой мы сталкиваемся с такими случаями, заключается в том, что мы действительно определяем бесконечно много экземпляров-исполнителей сразу - по одному для каждого возможного типа 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 

Теперь мы делаем наши функторов случаи, unproblematically, первый смотрит много, как, например, для 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 значения не были затронуты.

+0

Спасибо, это тоже очень приятное объяснение. Тем не менее, я принял пост FUZxxl в качестве ответа, потому что, помимо подтверждения моих рассуждений, он также дает мне интересный намек на интерпретацию «Либо b», как вычисление (монада) – MarcoS

1

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

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

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

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

(Либо строка) (Float) = Линейный поплавок.

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

Другими словами Либо это функтор, потому что есть такой очевидный БПМЖ по формуле:

  • для правых значений применяется п
  • для левых значений ничего не делать
4

Как уже упоминалось другие , 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 «последнего аргумента s, который на самом деле Either» s первый аргумент:

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 в аргументе первого типа 10, мы можем обернуть это значение в FlipEither и развернуть его позже. Например:

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

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

Определение Bifunctor из Either очень symetric:

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