В этом ответе используется абстракция Stream
, предоставляемая пакетом streaming. Эта абстракция:
- Является монадным трансформатором, поэтому вы можете поставить под него любую монаду.
- Имеет тип возврата отдельно от элементов, создаваемых потоком. Значение этого типа возвращается, когда поток исчерпан.
Представьте у вас есть функция, как:
module Main where
import Streaming
import Streaming.Internal (Stream(Effect,Step,Return))
import Streaming.Prelude
transformation :: Monad m => Stream (Of Int) m r -> Stream (Of String) m r
transformation = undefined -- your code here
transformation
изменяет поток Int
значений в поток String
значений. Он является полиморфным на базе монады, что означает, что само преобразование является чистым. Он является полиморфным по типу возврата r
, что означает, что преобразование всегда исчерпывает поток источника.
Теперь мы пишем эти вспомогательные определения:
data Feed a = Input a | EOF
trickster :: Monad m => Stream (Of a) (Stream ((->) (Feed a)) m)()
trickster = do
r <- Effect (Step (Return . Return)) -- request a `Feed a` value
case r of
Input a -> Step (a :> trickster)
EOF -> Return()
trickster
немного странно. На внешнем уровне это поток, который производит значения a
.Но внизу мы имеем что-то вроде монады Free (->)
(здесь также реализовано с Stream
), которое принимает значения a
и испускает их на внешнем уровне.
Что произойдет, если мы применяем trickster
к transformation
, а затем объединить два Stream
слоя с помощью функции unseparate
?
machine :: Stream (Sum (Of String) ((->) (Feed Int))) Identity()
machine = unseparate (transformation trickster)
Мы можем продвигать через machine
используя inspect
функцию
inspect :: (Functor f, Monad m) => Stream f m r -> m (Either r (f (Stream f m r)))
Вот страшный тип:
ghci> :t runIdentity (inspect machine)
runIdentity (inspect machine)
:: Either
()
(Sum
(Of String)
((->) (Feed Int))
(Stream (Sum (Of String) ((->) (Feed Int))) Identity()))
Это в основном означает, что на данном этапе машина либо завершается (но реализация trickster
гарантирует, что он никогда этого не сделает, если мы не пройдем EOF
), или он производит String
или требуется ввести значение Int
.
(Мы могли бы обойтись без unseparate
, но процесс отслаивания двух Stream
слоев была бы более запутанной.)
(Также см пост в блоге Programmatic translation to iteratees from pull-based code Пол Chiusano за оригинальную идею позади этого кода .)
Проблема, которую я вижу, заключается в том, что нет начальной точки; каждый автомат Мили исходит из некоторого предыдущего автомата, но откуда берется первый? Другими словами, вам нужно явно указать начальное состояние как аргумент 'toMealy'. – chepner
Вы можете, конечно, написать функцию 'toMealy :: (Stream a -> Stream b) -> Mealy a b', если вам все равно, что она делает, но я предполагаю, что у вас есть что-то более конкретное. Но не каждый трансформатор потока может быть создан «toStreamTransformer»; только те, где первые N элементов вывода зависят только от первых N элементов ввода для каждого N. –
Это не ответ на ваш вопрос, но вы можете взглянуть на «машинный» пакет Эдварда Кемца. https://hackage.haskell.org/package/machines-0.6.1 –