Различное поведение аппликатора на кортежах и списках в 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и<*>могут делать очень разные вещи для разных типов данных, пока они уважают законы.<*>"получает все комбинации", но также экземпляр, реализованный со вспомогательнымZipListnewtype, где<*>сжимает списки вместе иpureсоздает бесконечный список.