Различное поведение Applicative для кортежей и списков в 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], <*> объединяет их вместе.


person yuanqili    schedule 22.11.2015    source источник
comment
Hoogling Applicative приводит к этому _ 1_ определение, но для списков это это.   -  person Will Ness    schedule 22.11.2015
comment
[[Char], a -> a] или, в более общем смысле, [a,b] не является списком в haskell.   -  person Franky    schedule 22.11.2015
comment
О, спасибо, глупый. @Franky   -  person yuanqili    schedule 22.11.2015
comment
Спасибо, проблема решена. Но есть ли причина, по которой определение Applicative списка и кортежа таким образом? Или просто условность. @WillNess   -  person yuanqili    schedule 22.11.2015


Ответы (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 (,), когда оба аргумента типа должны быть одинаковыми. Например.,

instance Functor (\b -> (b,b)) where ...

но Haskell не позволяет этого. При необходимости нужна обертка newtype:

newtype P b = P (b,b)
instance Functor P where
   fmap f (P (x,y)) = P (f x, f y)
person chi    schedule 22.11.2015

Аппликатив - это любая комбинация типа данных и определений 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

Эти законы гарантируют, что <*> ведет себя очень похоже на приложение функции, но выполняется в некоем «особом контексте», который зависит от Applicative. В случае Maybe контекстом является возможное отсутствие значения. Для кортежей контекст - это «моноидальные аннотации, сопровождающие каждое значение».

pure и <*> могут делать очень разные вещи для разных типов данных, если они соблюдают законы.

Фактически, один и тот же тип данных может быть аппликативным по-разному. В списках есть экземпляр Applicative, в котором <*> "получает все комбинации", но также экземпляр, реализованный с помощью вспомогательного _ 9_ newtype, где <*> объединяет списки, а pure создает бесконечный список.

person danidiaz    schedule 22.11.2015