Вот класс функторов:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Обратите внимание, что "f" сам по себе является конструктором типа, потому что он применяется к переменной типа в строке fmap. Вот несколько примеров, чтобы прояснить это:
Тип конструкторов:
IO
Maybe
Either String
Типы:
IO Char
Maybe a
Either String String
"Maybe a" - это тип с одним конструктором типов ("Maybe") и одной переменной типа ("a"). Это еще не что-то конкретное, но его можно использовать в сигнатурах типов для полиморфных функций.
«Любой» - это конструктор типа, который принимает два аргумента типа, поэтому даже после применения одного (например, Either String
это все еще конструктор типа, поскольку он может принимать другой аргумент типа.
Суть этого заключается в следующем: когда вы определяете экземпляр Functor
, конструктор типа f
не может измениться. Это происходит потому, что он представлен одной и той же переменной f
, так как оба аргумент и результат fmap
. Единственный тип, который разрешено изменять, - это тип, применяемый к конструктору f
.
Когда вы пишете instance Functor (Either c)
, Either c
заполняется везде в объявлении fmap
. Это дает fmap следующий тип для этого экземпляра:
fmap :: (a -> b) -> (Either c) a -> (Either c) b
При определении Either
единственный полезный способ получить этот тип - применить значение Right
к функции. Помните, что «Любой» имеет два возможных значения с разными типами. Здесь значение Left
имеет тип 'c', поэтому вы не можете применить его к функции (которая ожидает 'a') [1], и результат также не будет правильным, потому что вы останетесь с Either b a
, что не соответствует определению класса.
После замены "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 теперь равен (Either d) a
. (Either c)
изменился на (Either 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» не находится в области видимости в классе определение.