Вы ищете термин бесплатный преобразователь монад. Лучшее место, чтобы узнать, как это работает, — это прочитать статью «Конвейеры сопрограмм» в выпуск 19 журнала The Monad Reader. Марио Блажевич дает очень ясное описание того, как работает этот тип, за исключением того, что он называет его типом «Корутина».
Я записал его тип в пакет transformers-free
, а затем он был объединен с пакетом free
, который является его новым официальным домом.
Ваш тип Callback
изоморфен:
type Callback a = forall r . FreeT ((->) a) IO r
Чтобы понять бесплатные преобразователи монад, вам нужно сначала понимать свободные монады, которые представляют собой просто абстрактные синтаксические деревья. Вы даете свободной монаде функтор, который определяет один шаг в синтаксическом дереве, а затем он создает Monad
из этого Functor
, который в основном представляет собой список этих типов шагов. Итак, если у вас было:
Free ((->) a) r
Это будет синтаксическое дерево, которое принимает ноль или более a
s в качестве входных данных, а затем возвращает значение r
.
Однако обычно мы хотим встроить эффекты или сделать следующий шаг синтаксического дерева зависимым от какого-либо эффекта. Для этого мы просто преобразуем нашу свободную монаду в свободный преобразователь монад, который чередует базовую монаду между шагами синтаксического дерева. В случае вашего типа Callback
вы чередуете IO
между каждым шагом ввода, поэтому ваша базовая монада IO
:
FreeT ((->) a) IO r
Преимущество свободных монад в том, что они автоматически являются монадами для любого функтора, поэтому мы можем воспользоваться этим преимуществом, чтобы использовать нотацию do
для сборки нашего синтаксического дерева. Например, я могу определить команду await
, которая будет связывать ввод внутри монады:
import Control.Monad.Trans.Free
await :: (Monad m) => FreeT ((->) a) m a
await = liftF id
Теперь у меня есть DSL для записи Callback
s:
import Control.Monad
import Control.Monad.Trans.Free
printer :: (Show a) => FreeT ((->) a) IO r
printer = forever $ do
a <- await
lift $ print a
Обратите внимание, что мне никогда не приходилось определять необходимый экземпляр Monad
. И FreeT f
, и Free f
автоматически становятся Monad
s для любого функтора f
, и в данном случае ((->) a)
— это наш функтор, поэтому он автоматически делает правильные вещи. Это магия теории категорий!
Кроме того, нам никогда не приходилось определять экземпляр MonadTrans
, чтобы использовать lift
. FreeT f
автоматически является монадным преобразователем для любого функтора f
, поэтому он позаботился и об этом за нас.
Наш принтер является подходящим Callback
, поэтому мы можем передавать ему значения, просто деконструируя свободный монадный преобразователь:
feed :: [a] -> FreeT ((->) a) IO r -> IO ()
feed as callback = do
x <- runFreeT callback
case x of
Pure _ -> return ()
Free k -> case as of
[] -> return ()
b:bs -> feed bs (k b)
Фактическая печать происходит, когда мы связываем runFreeT callback
, что затем дает нам следующий шаг в синтаксическом дереве, которому мы передаем следующий элемент списка.
Давай попробуем:
>>> feed [1..5] printer
1
2
3
4
5
Впрочем, вам даже не нужно писать все это самостоятельно. Как заметил Петр, моя библиотека pipes
абстрагирует для вас общие шаблоны потоковой передачи, подобные этому. Ваш обратный вызов просто:
forall r . Consumer a IO r
Мы бы определили printer
с помощью pipes
следующим образом:
printer = forever $ do
a <- await
lift $ print a
... и мы можем передать ему список значений следующим образом:
>>> runEffect $ each [1..5] >-> printer
1
2
3
4
5
Я разработал pipes
, чтобы охватить очень большой диапазон абстракций потоковой передачи, подобных этим, таким образом, чтобы вы всегда могли использовать нотацию do
для создания каждого компонента потоковой передачи. pipes
также поставляется с широким спектром элегантных решений для таких вещей, как обработка состояния и ошибок и двунаправленный поток информации, поэтому, если вы сформулируете свою Callback
абстракцию в терминах pipes
, вы бесплатно получите массу полезных механизмов.
Если вы хотите узнать больше о pipes
, я рекомендую вам прочитайте руководство.
person
Gabriel Gonzalez
schedule
06.02.2013
Free
< /a> --Free f a = Either a (f (Either a (f (Either a (f ...
-- иCofree
--Cofree f a = (a, f (a, f (a, f ...
-- кроме(->)
вместо суммы/произведения. Итак,T f a = a -> f (a -> f (a -> f ...
, что делает его контравариантным, в отличие от двух других. - person shachaf   schedule 06.02.2013(->)
не является коммутативным, вы также можете получитьnewtype Bar f a = Bar { unBar :: f (Bar f a) -> a }
, который являетсяFunctor
всякий раз, когдаf
равенContravariant
, а не наоборот.) - person shachaf   schedule 06.02.2013