Теперь, поиграв со Стрелками, отвечу на свой вопрос, используя функцию putStrLn
. Он имеет тип String -> IO ()
, то есть a -> m b
, поэтому метод следует обобщить на все провода Клейсли. Я также показываю, как управлять проводом, и результат удивительно прост.
Весь код написан на Literate Haskell, так что просто скопируйте его и запустите.
Во-первых, есть некоторые импорты для библиотеки Netwire 5.
import Control.Wire
import Control.Arrow
import Prelude hiding ((.), id)
Теперь это основа изготовления проволоки Клейсли. Предположим, у вас есть функция с типом a -> m b
, которую нужно поднять в провод. Теперь обратите внимание, что mkGen_
имеет тип mkGen_ :: Monad m => (a -> m (Either e b)) -> Wire s e m a b
Итак, чтобы сделать провод из a -> m b
, нам сначала нужно получить функцию с типом a -> m (Either () b)
. Обратите внимание, что левый блокирует провод, а правый активирует его, поэтому внутренняя часть имеет номер Either () b
вместо Either b ()
. На самом деле, если вы попробуете последнее, неясная ошибка компиляции скажет вам, что вы сделали это неправильно.
Чтобы получить a -> m (Either () b)
, сначала рассмотрим, как получить m (Either () b)
из m b
, мы извлекаем значение из монады (m b), поднимаем ее вправо, затем возвращаемся к монаде m. Короче: mB >>= return . Right
. Поскольку здесь нет значения «mB», мы делаем лямбда-выражение, чтобы получить a -> m (Either () b)
:
liftToEither :: (Monad m) => (a -> m b) -> (a -> m (Either () b))
liftToEither f = \a -> (f a >>= return . Right)
Теперь мы можем сделать проволоку Клейсли:
mkKleisli :: (Monad m, Monoid e) => (a -> m b) -> Wire s e m a b
mkKleisli f = mkGen_ $ \a -> (f a >>= return . Right)
Итак, давайте попробуем каноническую проводку «hello, world»!
helloWire :: Wire s () IO () ()
helloWire = pure "hello, world" >>> mkKleisli putStrLn
Теперь идет основная функция, чтобы проиллюстрировать, как управлять проводом. Обратите внимание, что по сравнению с исходным кодом testWire
в the Control.Wire.Run
из библиотеки Netwire, здесь нет необходимости использовать liftIO: внешняя программа ничего не знает о внутренней работе проводов. Он просто шагает по проводам, игнорируя то, что в нем находится. Maybe
это Just
означает лучшую композицию, чем использование Nothing
о Kleisli Wires? (Не каламбур!)
main = go clockSession_ helloWire
where
go s w = do
(ds, s') <- stepSession s
(mx, w') <- stepWire w ds (Right ())
go s' w'
А вот и код. К сожалению, StackOverflow не очень хорошо работает с Literate Haskell...
{-# LANGUAGE Arrows #-}
module Main where
import Control.Wire
import Control.Monad
import Control.Arrow
import Prelude hiding ((.), id)
mkKleisli :: (Monad m, Monoid e) => (a -> m b) -> Wire s e m a b
mkKleisli f = mkGen_ $ \a -> liftM Right $ f a
helloWire :: Wire s () IO () ()
helloWire = pure "hello, world" >>> mkKleisli putStrLn
main = go clockSession_ helloWire
where
go s w = do
(ds, s') <- stepSession s
(mx, w') <- stepWire w ds (Right ())
go s' w'
Обновить
Благодаря вдохновению Cubic. liftToEither
на самом деле может быть записано, как вы уже догадались, liftM
:
liftToEither f = \a -> liftM Right $ f a
mkKleisli f = mkGen_ $ \a -> liftM Right $ f a
person
Carl Dong
schedule
29.09.2015
mkGen_
и его тип в ответе, конкретная реализация, вероятно, менее интересна для будущих читателей). Если у вас есть дополнительные вопросы, задавайте их отдельно, а не меняйте вопросы. - person Cubic   schedule 24.09.2015putStrLn
в качестве более простого примера. - person Carl Dong   schedule 29.09.2015