Я новичок в Haskell и пытаюсь понять, как это работает?

sequenceA [(+3),(+2),(+1)] 3

Я начал с определения

sequenceA :: (Applicative f) => [f a] -> f [a]  
sequenceA [] = pure []  
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs

А затем развернул рекурсию в эту

(:) <$> (+3) <*> $ (:) <$> (+2) <*> $ (:) <$> (+1) <*> pure [] 
(:) <$> (+3) <*> $ (:) <$> (+2) <*> $ (:) <$> (+1) <*> []

Но здесь я не понимаю, для какого аппликативного функтора будет вызываться оператор <*>, для ((->) r) или для []

(:) <$> (+1) <*> []

Может кто-нибудь пошагово разобрать sequenceA [(+3),(+2),(+1)] 3 шаг за шагом? Спасибо.

7
Yola 6 Янв 2016 в 12:01

3 ответа

Лучший ответ

Он использует instance Applicative ((->) a).

Попробуйте это в ghci:

Prelude> :t [(+3),(+2),(+1)]
[(+3),(+2),(+1)] :: Num a => [a -> a]

Prelude> :t sequenceA
sequenceA :: (Applicative f, Traversable t) => t (f a) -> f (t a)

И шаблон соответствует типу аргумента: t = [], f = (->) a а аппликативное ограничение - на f.

3
d8d0d65b3f7cf42 6 Янв 2016 в 10:41

Если вы не можете согласиться с тем, что аргумент sequenceA [(+1)] волшебным образом применяется к ОБЕИМ (+1) и const [], это для вас. Это было для меня единственным камнем преткновения после того, как я понял, что pure [] = const [].

sequenceA [(+1)] = (:) <$> (+1) <*> const []

Использование лямбда-выражений (чтобы мы могли явно показывать и перемещать объекты, когда начинаем рассматривать приложение-функцию как функтор и аппликатив):

sequenceA [(+1)] = \b c -> ( (:) b c ) <$> ( \a -> (+1) a ) <*> ( \a -> const [] a )

И (<$>), и (<*>) являются инфиксной 4. Это означает, что мы читаем и оцениваем слева направо, т.е. мы начинаем с (<$>).

Где (<$>) :: Functor f => (a -> b) -> f a -> f b.

Эффект <$> состоит в том, чтобы вытащить (+1) из оболочки ((->) r), ИЛИ \a -> из нашего лямбда-кода и применить его к \b c -> ( (:) b c ), где он будет место b, затем повторно примените оболочку (это \a, который появляется после знака равенства в строке ниже):

sequenceA [(+1)] = \a c -> ( (:) ((+1) a) c ) <*> ( \a -> const [] a )

Обратите внимание, что (:) все еще ожидает аргумента c, а (+1) все еще ожидает a. Теперь мы переходим к аппликативной части.

Помните, что: (<*>) :: f (a -> b) -> f a -> f b. Наш f - это приложение-функция \a ->.

Обе стороны теперь имеют одну и ту же оболочку, а именно \a ->. Я сохраняю здесь a, чтобы напомнить нам, где позже будут применяться a, так что здесь это становится немного псевдо-у. Приложение-функция будет подключено в мгновение ока . Обе функции зависят от одного и того же a именно потому, что у них была одна и та же оболочка приложения-функции, то есть аппликатив. Без их \a -> оберток (спасибо <*>) это выглядит так:

( \c -> ( (:) ((+1) a) c ) ) (const [] a)

= ( (:) ((+1) a) (const [] a) ) -- Ignore those a's, they're placeholders.

Теперь последнее, что делает <*>, - это помещает этот результат обратно в свою оболочку \a ->:

sequenceA [(+1)] = \a -> ( (:) ((+1) a) (const [] a) )

Небольшое подслащивание этого дает:

sequenceA [(+1)] = \a -> (+1) a : const [] a

Увидеть! Совершенно логично, что аргумент sequenceA [(+1)] идет как к (+1), так и к const. Например, применение 2 дает:

sequenceA [(+1)] 2 = (+1) 2 : const [] 2

Помните, что const a b :: a -> b -> a, и поэтому просто игнорирует его ввод:

sequenceA [(+1)] 2 = 3 : []

ИЛИ, более сладко:

sequenceA [(+1)] 2 = [3]
3
Paul Parker 20 Дек 2017 в 06:20

Это видно по типу последовательности S:

sequenceA :: (Applicative f, Traversable t) => t (f a) -> f (t a)

Внешний тип аргумента должен быть Traverable, а его внутренний тип - Applicative.

Теперь, когда вы задаете sequenceA список функций (Num a) => [a -> a], список будет Traversable, а элементы внутри списка должны быть Applicative. Следовательно, он использует аппликативный экземпляр для функций.

Поэтому, когда вы применяете последовательность A к [(+3),(+2),(+1)], строится следующее вычисление:

sequenceA [(+3),(+2),(+1)] = (:) <$> (+3) <*> sequenceA [(+2),(+1)]
sequenceA [(+2),(+1)]      = (:) <$> (+2) <*> sequenceA [(+1)]
sequenceA [(+1)]           = (:) <$> (+1) <*> sequenceA []
sequenceA []               = pure []

Посмотрим на последнюю строчку. pure [] берет пустой список и помещает его в некоторую аппликативную структуру. Как мы только что видели, аппликативная структура в этом случае - ((->) r). Из-за этого sequenceA [] = pure [] = const [].

Теперь строку 3 можно записать как:

sequenceA [(+1)] = (:) <$> (+1) <*> const []

Комбинирование функций таким образом с <$> и <*> приводит к параллельному применению. (+1) и const [] применяются к одному аргументу, а результаты объединяются с помощью (:)

Поэтому sequenceA [(+1)] возвращает функцию, которая принимает значение типа Num a => a, применяет к нему (+1), а затем добавляет результат к пустому списку, \x -> (:) ((1+) x) (const [] x) = \x -> [(+1) x] .

Эта концепция может быть расширена до sequenceA [(+3), (+2), (+1)]. Результатом является функция, которая принимает один аргумент, применяет все три функции к этому аргументу и объединяет три результата с (:), собирая их в список: \x -> [(+3) x, (+2) x, (+1) x].

8
Will Ness 17 Дек 2017 в 18:58