Примеры адаптивных трансформаторов 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 показывает вам, как это сделать.