Изучите Вас, у Haskell есть пример о функторах. Я могу считать 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
Вот класс функтора:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Обратите внимание, что «f» сам по себе является конструктором типа, потому что он применяется к переменной типа в строке fmap. Вот несколько примеров, чтобы прояснить это:
Конструкторы типов:
IO
Maybe
Either String
Типы:
IO Char
Maybe a
Either String String
«Может быть, а» - это тип с одним конструктором типа («Может быть») и одной переменной типа («а») . Это еще не что-то конкретное, но его можно использовать в сигнатурах типов для полиморфных функций.
«Either» - это конструктор типа, который принимает два аргумента типа, поэтому даже после того, как вы применили один (например, Either String
, он по-прежнему является конструктором типа, потому что он может принимать аргумент другого типа.
Суть из этого: когда вы определяете экземпляр Functor
, конструктор типа f
не может измениться. Это потому, что она представлена той же переменной, f
, как аргумент и результат fmap
. Единственный тип, который разрешено изменять, - это тип, применяемый к конструктору f
.
Когда вы пишете instance Functor (Either c)
, Либо c
заполняется для f
везде в объявлении fmap
. Это дает fmap следующий тип для этого экземпляра:
fmap :: (a -> b) -> (Either c) a -> (Either c) b
С определением Either
, единственный полезный способ получить этот тип - применить к функции значение Right
. Помните, что «Either» имеет два возможных значения с возможно разными типами. Здесь значение Left
имеет тип 'c', поэтому вы не можете применить его к функции (которая ожидает 'a') [1], и результат также будет неправильным, потому что вы ' d останется с Либо ba
, что не соответствует определению класса.
После замены «f» на «Either c», чтобы получить указанную выше сигнатуру типа для fmap с экземпляром «Either c», следует написать реализацию. Следует рассмотреть два случая: левый и правый. Сигнатура типа говорит нам, что тип левой стороны, «c», не может измениться. У нас также нет возможности изменить значение, потому что мы не знаем, какой он тип на самом деле. Все, что мы можем сделать, это оставить это в покое:
fmap f (Left rval) = Left rval
Для правой стороны сигнатура типа говорит, что мы должны изменить значение с типом «a» на значение с типом «b».Первый аргумент - это функция, которая делает именно это, поэтому мы используем функцию с входным значением, чтобы получить новый результат. Их объединение дает полное определение
instance Functor (Either c) where
fmap f (Right rval) = Right (f rval)
fmap f (Left lval) = Left lval
. Здесь работает более общий принцип, поэтому создание экземпляра Functor, который регулирует левую сторону, невозможно, по крайней мере, с определениями Prelude. Копирование некоторого кода сверху:
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, будет теперь (Либо d) a
. (Либо c)
изменилось на (Либо d)
, но это запрещено, потому что нет способа выразить его в классе Functor. Чтобы выразить это, вам понадобится класс с двумя переменными типа, например.
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
В этом классе, поскольку переменные типа left и right находятся в области видимости, можно написать методы, которые работают с одной (или обеих) сторон.
instance BiFunctor Either where
lMap = leftMap
rMap = rightMap --the same as the standard fmap definition
biMap fl fr e = rMap fr (lMap fl e)
Хотя на практике люди обычно просто пишут «biMap» для класса BiFunctor и используют «id» для другой функции, если требуется левое или правое отображение.
[1] Точнее, значение Left имеет тип 'c', функция ожидает 'a', но средство проверки типов не может объединить эти типы, потому что тип 'c' не входит в область видимости в определении класса.
Left и Right не являются типами, а Left x
и Right y
относятся к одному типу. Они просто конструкторы Either. Вы можете рассмотреть
Left :: c -> Either c d
Right :: d -> Either c d
У вас может быть 2 объявления fmap
, потому что мы знаем, что Left и Right - разные значения. Это похоже на
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
Обратите внимание, что a
и b
являются произвольными типами. Для ясности давайте переименуем a
в вашем экземпляре в c
, а функцию f
в func
.
instance Functor (Either c) where
fmap func (Right x) = Right (x)
fmap func (Left x) = Left (func x)
Предположим, что ваш Either следует определению по умолчанию
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
. Оба они недействительны, учитывая, что a
, b
и c
являются независимыми произвольными типами.
Экземпляр, который вы пытаетесь написать, назовем его fmap2
, имеет следующий тип:
fmap2 :: (a -> b) -> Either a c -> Either b c
Если вы установите LANGUAGE
] pragma TypeOperators
, GHC также принимает тип
fmap2 :: (a -> b) -> (a `Either` c) -> (b `Either` c)
В идеальном мире это также будет работать:
fmap2 :: (a -> b) -> (`Either` c) a -> (`Either` c) b
, который даст экземпляр Functor для (`Either` c)
, но сходство между обычными операторами (и их разделами) и операторами типов исчезает на этом этапе (если нет опции GHC, которую мне не хватает!)
Короче говоря: ваше понимание функторов в порядке, но вас укусила отсутствие лямбд на уровне типов.
Надеюсь, это поможет ...
Но сначала немного предыстории:
1) Функтору нужен «конструктор типа», (ну, не тип как таковой , ...) тип, которому «нужен» другой обычный тип, данный ему для формирования «полного типа», точно так же, как универсальный тип в Java / C ++.
Так, например, List
является Functor (это, кстати) или Array
, потому что (среди прочего) полный тип не является t только Список
, но Список
. Так, :
Функтор принимает «конструктор типа», неполный тип.
2) Либо
- это тип конструктора, который люди из Haskell (читай: Эдвард Кметт и другие математики-математики) называют бифунктором. Чтобы оно было полным, ему нужны два типа. Например, полное использование Either: Either String
, что означает (да, да, «да!») Либо целое ( Left
), либо ( ] Правый
) Строка.Таким образом, это означает, что Either Integer
является неполным типом, который является либо Left Integer
, либо Right ... b
, когда вы
решить, какой должна быть эта буква "Ъ".
А теперь самое интересное!
Главное работает, потому что fmap
использует некоторый конструктор типов и использует его с функцией a -> b
для создания аналогичной функции из fa
to fb
- самый популярный пример этого в Haskell - это пример для списков, AKA map :: (a -> b) -> ([a] -> [b])
, где Functor - это часть []
. Теперь, используя что-то вроде Либо
(давайте продолжим и воспользуемся Either Integer
из ранее), подпись типа fmap выглядит так:
fmap :: (a -> b) -> (Either Integer a -> Either Integer b)
и два примера (из верхней части ) показывают, что fmap делает с репрезентативными значениями этого типа Either Integer a
, чтобы получить значение типа Either Integer b
.
Теперь ваш -bottom- не работает, потому что:
f
, которая принимает
a
s до b
s. Left
должен быть типом
Целое число и оставаться целым числом (или
введите Float и оставайтесь Float, что
ever type - это левый один из
два типа типа Либо
вы используете). 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
, но даже не согласованы друг с другом!
Мне жаль, что я написал полкниги, чтобы продраться, но я надеюсь, что это даст вам некоторое представление.
Хотя в конечном итоге я буду придерживаться вашего формата, я собираюсь начать с чего-то в немного другом формате, поскольку я думаю, что это сделает мое объяснение более ясным.
Давайте рассмотрим другой тип данных
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
Вы должны быть в состоянии понять, почему это не работает. Типом fmap
является Functor f => (a -> b) -> f a -> f b
; в данном контексте это (a -> b) -> Choice a -> Choice b
. Таким образом, аргумент f
имеет тип a -> b
. Однако во втором (неудачном) объявлении экземпляра вы пишете f i
. Из объявления типа данных мы знаем, что i
должно быть Integer
, поэтому мы не можем применить к нему f
. Аналогично, поскольку a
имеет тип a
, Chosen 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
для Either
- оно и не может. То, что является экземпляром Functor
, должно принимать один параметр типа. Таким образом, Int
не может быть функтором, поскольку Int
не принимает параметров типа, но []
и Maybe
могут быть, поскольку [a]
и Maybe a
- полные типы. Either
, однако, принимает два параметра типа: Either a b
. Таким образом, этот экземпляр делает то, что объявляет, что Either c
является функтором для любого возможного c
. Этот c
является фиксированным на время объявления экземпляра. Итак, давайте пройдемся и добавим типы (это не законный синтаксис! ):
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
. Аналогично, мы должны применить f
к a
, чтобы получить что-то типа b
.
Именно поэтому ваш нижний пример не работает: у вас есть функция, которая должна работать для любого типа, применяемая только к фиксированному типу 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
, что по-прежнему недопустимо.
В основном, проблема возникает из-за этого. Во-первых, обратите внимание, что f
одинаково в двух пунктах определения функции, поэтому оно не может меняться между строками. Во-вторых, обратите внимание, что мы исправляем c
и объявляем для экземпляра c
. Это верно для любого c
, но мы рассматриваем только одно за раз. Наконец, из-за этого аргумент Left
не параметризован типом, который ожидает f
; он гарантированно имеет некоторый фиксированный тип c
. Это означает, что (а) вы не можете применить к нему f
, и (б) вы должны применить его к аргументу Right
, поскольку иначе вы не измените тип, который ожидается изменить.
Хорошо, вот еще одна очень простая попытка.
Вы спрашиваете, почему это не компилируется:
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
Итак, давайте заменим Либо c
вместо f в сигнатуре fmap
(я переименовал Либо
в ] Либо c
, чтобы наши два разных a
не перепутались):
fmap :: (a -> b) -> Either c a -> Either c b
Вы видите проблему? Ваша функция совершенно верна - просто у нее другой тип, чем у fmap для . Либо
обязательно должен иметь .
Это своего рода прекрасная вещь о типах. Учитывая сигнатуру для fmap
, на самом деле существует только одна значимая реализация для fmap
на Либо a.
Иногда, когда нам везет и мы осторожны, мы можем оказаться в аналогичных ситуациях - с учетом сигнатуры типа функция почти пишет сама себя.
Edit : пытаюсь ответить на вопросы ниже.
1) Не происходит «композиции двух функций».Чтобы получить сигнатуру типа для fmap
вместо Либо
, просто пройдите через сигнатуру функции fmap
, и везде, где вы видите f
, замените это с Либо с
. Мы бы назвали это «специализацией» сигнатуры типа fmap. Другими словами, он строго менее общий , чем обычный тип fmap - везде, где требуется функция более специализированного типа, вы можете без проблем передать что-то общего типа.
2) Ваша функция для отображения на левой стороне (которую я назвал «foo» в приведенных выше примерах) вполне подойдет. Он отлично работает, он делает то, что вы хотите. Вы просто не можете назвать его fmap
и использовать в экземпляре Functor. Лично я бы назвал его примерно так onLeft
или mapLeft
.
Все следующее может быть проигнорировано / предназначено для информации, но не является предложением для будущего чтения в ближайшем будущем / фактического использования:
Если кто-то хочет получить очень техническую информацию, потому что вы можете нанести карту как на левую, так и на левую сторону. с правой стороны (хотя вы можете объявить Functor только для последнего), Either - это не только 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.
Хотите верьте, хотите нет, это не волшебство. Все это связано с типом Либо a b
не является тем же типом, что и , либо b a
. Вот определение Either из Prelude
data Either a b = Left a | Right b
... Обратите внимание, что сначала идет переменная типа a, затем b, а также обратите внимание, что мы включаем только a в объявление функтора Either:
instance Functor (Either a) where
fmap f (Right x) = Right (f x)
fmap f (Left x) = Left x
Теперь давайте посмотрим на определение функтора Maybe
instance Functor Maybe where
fmap = map
Здесь нет переменной типа, хотя Maybe
принимает один параметр типа (например, Maybe Int
). Я пытаюсь понять, что типы - это не функторы, а конструкторы типов - это функторы (у функторов есть вид * -> *
).
Итак, пусть f :: b -> c
, в версии Either Functor, которая работает, x
из (Left x)
имеет введите a
, что нормально, поскольку это (Либо a)
, который является функтором, x
в (Right x)
имеет Введите b
, поэтому (Правый x)
имеет тип ((Либо a) b)
, а (Правый (fx))
- типа ((Либо a) c)
, поэтому fmap имеет тип (b-> c) -> ((Либо a) b) -> ((Либо a) c)
, как требуется.
В вашей версии, которая потерпела неудачу, мы имеем, что x
в (Right (x))
не относится к типу a
, а к типу b
, поэтому (Right (x))
является не типа ((Either a) c)
, что не соответствует типу fmap.
подведем итог: получается работающий fmap: (b -> c) -> (Либо a) b -> (Либо a) c
,
но выходит тот, который не работает: (b -> c) -> (Либо b) a -> (Либо c) a
, и это не тот тип для fmap.
(Отредактируйте, чтобы попытаться лучше ответить на вопрос)
Определение Either:
data Either a b = Left a | Right b
Итак, «Either» принимает два аргумента типа. Между прочим, технически «Either» на самом деле не тип, а конструктор типа; для создания типа требуются аргументы типа.
Определение Functor:
class Functor f where
fmap :: (p -> q) -> f p -> f q
Итак, в этом определении класса любой тип «f», который является экземпляром Functor, должен принимать аргумент типа. Это не декларируется; это выводится из «f p» и «f q»; поскольку здесь "f" задается параметр типа, он должен быть типом, который его принимает.
(Примечание: в исходном определении использовались «a» и «b» вместо «p» и «q». Я использую разные буквы, чтобы отличать слова от «Either ab», когда я перейду к этому позже)
В большинстве случаев «f» - это контейнерный тип, такой как список или дерево. Так, например, у вас есть
data Tree a = ...
instance Functor Tree where
fmap func_a2b tree_of_a = ... -- tree of b.
. Однако «Либо» принимает два параметра типа, так как мы можем вписать его в эту схему? Ответ заключается в том, что типы могут иметь частичное применение, как и функции. Точно так же, как Я могу объявить функцию
foo x y = ...
, а затем сказать «foo 2», чтобы получить новую функцию, которая ожидает второй аргумент, поэтому я могу сказать «Либо a», чтобы получить новый тип, который ожидает второй аргумент типа.
Теперь посмотрим на исходный экземпляр:
instance Functor (Either a) where ....
Итак, здесь «Either a» - это конструктор типа, который ожидает еще один аргумент, точно так же, как Functor ожидает от своих экземпляров. Таким образом, тип «fmap» для «Either a» будет
fmap :: (p -> q) -> Either a p -> Either a q
Итак, теперь в предложении «where» вы должны дать определение «fmap», имеющее этот тип.Первый, который вы цитируете, имеет этот тип, потому что второй параметр типа используется для конструктора «Right», и именно к нему применяется функция. Второй не будет работать, потому что он будет иметь тип
fmap :: (p -> q) -> Either p a -> Either q a
. И это не то, что класс Functor утверждает, что он будет.
Top работает, потому что fmap: :( b -> c) -> Либо ab -> Либо ac
и ваш -bottom- не работают, потому что это могло бы require fmap: :( a -> c) -> Либо ab -> Либо ac
. Но это сработает, если вы измените 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)
Эм ... Как насчет "видов"? ..
Думаю, это может упростить понимание.
Помните, что такое карри. Т.е. в ghci:
Prelude> let f x y z = x + y * z f :: (Num a) => a -> a -> a -> a Prelude> :t f 1 f 1 :: (Num t) => t -> t -> t Prelude> :t f 1 2 f 1 2 :: (Num t) => t -> t Prelude> :t f 1 2 3 f 1 2 3 :: (Num t) => t
То же, что и с типами. Когда вы говорите Либо
тип этого типа * -> * -> *
(т.е. он принимает два типа и производит тип), либо когда вы говорите Либо
kind является * -> *
, а для Либо ab
это *
(кстати, Monad a
и Functor a
требует a
должно быть вроде * -> *
, насколько я помню).
Итак, когда вы говорите type Либо a
, что означает тип, который все еще является неполным (требуется привязка некоторого «аргумента»), поэтому fmap :: (a -> b) -> fa -> fb
становится fmap :: (a -> b) -> (Либо c) a -> (Либо c) b
, когда f
заменяется на Либо c
].