Примеры адаптивных трансформаторов Haskell

Вики на www.haskell.org рассказывают нам о Applicative Transformers:

Итак, где аппликативные трансформаторы? Ответ заключается в том, что для аппликативных функторов нам не нужны специальные трансформаторы, поскольку они могут быть объединены общим образом. http://www.haskell.org/haskellwiki/Applicative_functor#Applicative_transfomers

Я попробовал следующее, чтобы попытаться объединить кучу аппликативных функторов. Но все, что у меня было, было кучей ошибок. Вот код:

import Control.Applicative
import System.IO

ex x y = (:) <$> x <*> y 
test1 = ex "abc" ["pqr", "xyz"]  -- only this works correctly as expected
test2 = ex "abc" [Just "pqr", Just "xyz"]
test3 = ex "abc" (Just "pqr")
test4 = ex (Just 'a') ["pqr", "xyz"]
test5 = ex (return ("abc"):: IO ()) [Just "pqr", Just "xyz"]

Это вызывает множество ошибок типа, которые, хотя я могу частично понять, я не смог их разрешить вообще.

Ошибки приведены в конце.

Итак, как я могу объединить аппликатор Maybe и аппликатор списка, например?

Как я могу объединить пример State Applicative и Application List? Есть ли какие-либо другие примеры, допустим, объединение "Возможно и списка", "Может быть, и состояние" и, наконец, ужас всех заявок на участие в работе ИО и штата?

Спасибо.

Ниже перечислены сообщения об ошибках GHCi.

example.hs:6:19:
    Couldn't match expected type `[Char]' with actual type `Maybe a0'
    In the return type of a call of `Just'
    In the expression: Just "pqr"
    In the second argument of `ex', namely `[Just "pqr", Just "xyz"]'

example.hs:7:19:
    Couldn't match expected type `[[Char]]' with actual type `Maybe a0'
    In the return type of a call of `Just'
    In the second argument of `ex', namely `(Just "pqr")'
    In the expression: ex "abc" (Just "pqr")

example.hs:8:23:
    Couldn't match expected type `Maybe' with actual type `[]'
    In the second argument of `ex', namely `["pqr", "xyz"]'
    In the expression: ex (Just 'a') ["pqr", "xyz"]
    In an equation for `test4': test4 = ex (Just 'a') ["pqr", "xyz"]

example.hs:9:21:
    Couldn't match expected type `()' with actual type `[Char]'
    In the first argument of `return', namely `("abc")'
    In the first argument of `ex', namely `(return ("abc") :: IO ())'
    In the expression:
      ex (return ("abc") :: IO ()) [Just "pqr", Just "xyz"]
Failed, modules loaded: none.
Prelude>

Ответ 1

В статье wiki говорится, что liftA2 (<*>) можно использовать для создания аппликативных функторов. Легко понять, как использовать его из своего типа:

o :: (Applicative f, Applicative f1) =>
     f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)
o = liftA2 (<*>)

Итак, если f - Maybe и f1 - [], получим:

> Just [(+1),(+6)] `o` Just [1, 6] 
Just [2,7,7,12]

Другой способ:

>  [Just (+1),Just (+6)] `o` [Just 1, Just 6]
[Just 2,Just 7,Just 7,Just 12]

Поскольку @McCann говорит, что ваша функция ex эквивалентна liftA2 (:):

test1 = liftA2 (:) "abc" ["pqr", "xyz"]

Чтобы использовать (:) с более глубоким аппликативным стеком, вам нужно несколько приложений liftA2:

*Main> (liftA2 . liftA2) (:) (Just "abc") (Just ["pqr", "xyz"])
Just ["apqr","axyz","bpqr","bxyz","cpqr","cxyz"]

Однако он работает только тогда, когда оба операнда одинаково глубоки. Поэтому, кроме double liftA2, вы должны использовать pure для исправления уровня:

*Main> (liftA2 . liftA2) (:) (pure "abc") (Just ["pqr", "xyz"])
Just ["apqr","axyz","bpqr","bxyz","cpqr","cxyz"]

Ответ 2

Рассмотрим следующие типы сигнатур:

liftA2 :: (Applicative f) => (a -> b -> c) -> f a -> f b -> f c
(<*>) :: (Applicative f) => f (a -> b) -> f a -> f b

Комбинированный, результирующий тип:

liftA2 (<*>) :: (Applicative f, Applicative g) 
             => f (g (a -> b)) -> f (g a) -> f (g b)

Это действительно комбинация двух Applicative s. На самом деле это комбинация ровно двух Applicative s. Другими словами, хотя вы можете комбинировать Applicative в общем виде, это никак не выполняется автоматически. Все должно быть явно поднято правильное количество раз.

Ваша функция ex эквивалентна liftA2 (:), которая имеет тип (Applicative f) => f a -> f [a] -> f [a]. Просматривая ваши примеры, сделав некоторые догадки о том, что вы хотели сделать:

test1 = ex "abc" ["pqr", "xyz"]

Здесь f есть [], и мы применяем его к аргументам типа [Char] и [[Char]].

test2 = ex "abc" [Just "pqr", Just "xyz"]

Второй аргумент имеет тип [Maybe [Char]], поэтому нам нужно подняться дважды. Первый аргумент также нужно снять, так как он имеет тип [Char] и должен быть [Maybe Char].

test3 = ex "abc" (Just "pqr")

На этот раз второй аргумент имеет тип Maybe [Char], поэтому f - Maybe, и нам нужен только один подъем. Поэтому первый аргумент должен иметь тип Maybe Char.

test4 = ex (Just 'a') ["pqr", "xyz"]

На этот раз первый аргумент Maybe Char, а второй - [[Char]], поэтому у вас есть два совершенно разных Applicative s; оба нужно будет снять, чтобы дать вам либо [Maybe Char], либо Maybe [Char].

test5 = ex (return ("abc"):: IO ()) [Just "pqr", Just "xyz"]

Подпись типа здесь не имеет смысла; вы, вероятно, хотели IO [Char]. Второй аргумент имеет тип [Maybe [Char]]. Как и в предыдущем примере, они не совпадают, но на этот раз у вас есть три Applicative s. Если вы хотите что-то вроде IO [Maybe a], вам нужно будет поднять (:) все три раза, например. liftA2 (liftA2 ex).

Этот способ объединения Applicative называется "functor composition", а страница, на которую вы ссылаетесь, упоминает библиотеки, которые определяют явный конструктор типа композиции. Например, используя библиотеку transformers, для описания вашего пятого примера у вас может быть тип типа Compose IO (Compose [] Maybe). Этот скомпонованный тип определяется как экземпляр Applicative в вышеупомянутом общем роде и применяет правильное количество операций подъема. Недостатком является то, что вам нужно будет обернуть и развернуть слои newtype, которые потребуются.


В качестве дополнения это утверждение:

Итак, где аппликативные трансформаторы? Ответ заключается в том, что нам не нужны специальные трансформаторы для аппликативных функторов, поскольку они могут быть объединены общим способом.

... немного фиктивный. Правда, композиция из двух Applicative также Applicative, но это не единственный способ объединить Applicative s!

Рассмотрим StateT s m a, что эквивалентно s -> m (s, a), хотя оно несколько отличается. Это также можно было бы записать как состав из трех функторов: ((->) s), m и ((,) s), и результирующий экземпляр Functor был бы правильным, но экземпляр Applicative был бы полностью неправильным. Если вы начинаете только с State s a = s -> (a, s), нет способа определить StateT s m, составив State s и m.

Теперь заметим, что комбинация non-composition StateT s (Either e) представляет собой упрощенную версию типичной монады-сборщика синтаксиса, используемой в таких библиотеках, как Parsec, и такие синтаксические анализаторы являются одним из известных мест, где использование стиля Applicative популярный. Как таковой, кажется более чем немного вводящим в заблуждение предположить, что сочетания типа трансформатора монады как-то ненужны или излишни, если речь идет о Applicative!

Ответ 3

Говорить, что их можно комбинировать в общем виде, не означает, что они могут быть объединены неявно или невидимо или что-то в этом роде. =)

Вам все равно нужно написать немного кода, либо используя разные mungers, чем <*> и pure, или добавив некоторый шум newtype. Например, используя TypeCompose пакет, вы можете написать

test2 = ex (O (Just "abc")) [O (Just "pqr"), O (Just "xyz")]

Ответ 4

Как обычно, здесь полезно сосредоточиться на типах, чтобы выяснить, какой должен быть состав аппликативных функторов.

Если мы пишем a для типа конкретного чистого значения x, поэтому он не имеет побочных эффектов, то мы можем поднять это чистое значение на вычисление аппликативного функтора f с помощью pure комбинатор. Но тем не менее мы можем использовать функцию pure из экземпляра g Applicative, чтобы поднять pure x в функтор g.

pure (pure x) :: g (f a)

Теперь g (f a) - это тип вычислений, объединяющих эффекты g и эффекты f. Посмотрев на ваши тесты, мы замечаем, что

test1 :: [String]

Вы использовали только один эффект в test1, то есть недетерминизм, который дает вам экземпляр списка Applicative. Действительно, сломав его:

"abc" :: String
((:) <$>) :: [Char] -> [String -> String]
((:) <$> "abc") :: [String -> String]
((:) <$> "abc" <*> ["pqr", "xyz"]) :: [String]

Теперь, если мы хотим создать эффект отказа и эффект недетерминированности, мы ожидаем построить вычисление типа Maybe [a] или, возможно, [Maybe a]. Оказывается, эти два эквивалентны, так как аппликативные функторы всегда коммутируют.

Здесь вычисляется тип [Maybe Char]. Он не детерминистически возвращает a Char, но если это произойдет, это может привести к ошибке:

x1 = [Just 'a', Just 'b', Just 'c']

Аналогично, здесь вычисление типа [Maybe String]:

x2 = [Just "pqr", Just "xyz"]

Теперь мы хотим поднять (:) на этот комбинированный аппликативный функтор. Чтобы сделать это, мы должны поднять его дважды:

pure (pure (:)) :: [Maybe (Char -> String -> String)]

Аналогично, чтобы применить его, нам нужно вытолкнуть это вычисление через оба функтора. Поэтому мы можем ввести новый комбинатор (<<*>>), который делает следующее:

(<<*>>) :: (Applicative f, Applicative f1) =>
           f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)
(<<*>>) = liftA2 (<*>)

Что теперь позволяет нам написать:

pure (pure (:)) <<*>> x1 <<*>> x2

который вы можете проверить, имеет ожидаемый тип.

Но поскольку аппликативные функторы замкнуты относительно композиции, [Maybe a] сам является аппликативным функтором, поэтому вы можете захотеть повторно использовать pure и (<*>). Модуль Data.Functor.Compose пакета transformers показывает вам, как это сделать.