Различное поведение аппликатора на кортежах и списках в Haskell
Например,
-- Num a => ([Char], a -> a) <*> ([Char], a)
> ("hello ",(*6)) <*> ("world",7)
("hello world",42)
-- Num a => [a -> a] <*> [a]
> [(*7),(*6)] <*> [6,7]
[42,49,36,42]
-- Num a => [[Char], a -> a] <*> [[Char], a]
> ["hello ",(*6)] <*> ["world",7]
<interactive>:17:2:
Couldn't match expected type ‘[Char] -> [Char]’
with actual type ‘[Char]’
In the expression: "hello "
In the first argument of ‘(<*>)’, namely ‘["hello ", (* 6)]’
In the expression: ["hello ", (* 6)] <*> ["world", 7]
Для трех примеров, <*>
показывает различное поведение. Что же происходит? Почему в третьем случае он ожидает [Char] -> [Char]
, а не [Char]
, как в первом случае. Более того, даже есть только [Char]
в кортежах, <*>
объединяет их вместе.
2 ответа:
Разница заключается в том, что списки однородны, а кортежи-нет: списки содержат только элементы одного типа, а кортежи-нет.
Даже не глядя на аппликативы, функторы уже показывают основное различие:
Было бы нелогично утверждать, чтоfmap succ [1,2,3] ==> [2,3,4] fmap succ ("a", 4) ==> ???
fmap
применимоsucc
к"a"
. Происходит то, что только второй компонент подвергается воздействию:fmap succ ("a", 4) ==> ("a", 5)
Действительно, посмотрите на примеры:
instance Functor [] where ... instance Functor ((,) a) where ...
Обратите внимание на
Обратите внимание, что теоретически возможно допустить экземплярa
тип. В экземпляре списка[]
принимает только один параметр типа, и это тип, на который влияетfmap
. В(,)
мы имеем два параметра типа: один фиксирован (кa
) и не изменяется при примененииfmap
- только второй.Functor (,)
, когда оба аргумента типа вынуждены быть одинаковыми. Например,Но Хаскелл этого не допускает. Если нужно, нужна обертка newtype:instance Functor (\b -> (b,b)) where ...
newtype P b = P (b,b) instance Functor P where fmap f (P (x,y)) = P (f x, f y)
Аппликатив - это любая комбинация типа данных и определений
pure
и<*>
, которая удовлетворяет аппликативу . законы :Эти законы гарантируют, что[identity] pure id <*> v = v [composition] pure (.) <*> u <*> v <*> w = u <*> (v <*> w) [homomorphism] pure f <*> pure x = pure (f x) [interchange] u <*> pure y = pure ($ y) <*> u
<*>
ведет себя очень похоже на приложение функции, но происходит в некотором "особом контексте", который зависит от приложения. Для случаяMaybe
контекст-это возможное отсутствие значения. Для кортежей контекст-это "моноидальные аннотации, сопровождающие каждый кортеж". значение".На самом деле, один и тот же тип данных может быть Применительным по-разному. Списки имеют прикладной экземпляр, в котором
pure
и<*>
могут делать очень разные вещи для разных типов данных, пока они уважают законы.<*>
"получает все комбинации", но также экземпляр, реализованный со вспомогательнымZipList
newtype, где<*>
сжимает списки вместе иpure
создает бесконечный список.