Потоковый трансформатор Monad для Vector.Stream - PullRequest
0 голосов
/ 02 июля 2018

Data.Vector.Stream обеспечивает хорошую реализацию Stream, которая очень эффективна благодаря фокусированию на плавкость (подробнее см. в этой статье ). Поскольку vector-0.1 эта реализация Stream немного изменилась, переместив тип Step в монаду. (Теперь реализация находится в Data.Vector.Fusion.Stream.Monadic.)

В двух словах, вот определение Stream:

data Step s a where
    Yield :: a -> s -> Step s a
    Skip  :: s -> Step s a
    Done  :: Step s a

data Stream a = forall s. Stream (s -> Step s a) s

Step s a инкапсулирует три возможных результата из одной итерации состояния s с функцией обновления s -> Step s a. Поток либо Done, либо пропускает вывод, либо выдает вывод. (В приведенном выше определении используются GADT, но здесь это не имеет значения.)

Простые приложения этого Stream:

empty :: Stream a
empty = Stream (const Done) ()

singleton :: a -> Stream a
singleton x = Stream step True where
    step True  = Yield x False
    step False = Done

fromList :: [a] -> Stream a
fromList zs = Stream step zs
where
    step (x:xs) = Yield x xs
    step []     = Done

Строгий левый сгиб делается так:

foldl'S :: (a -> b -> a) -> a -> Stream b -> a
foldl'S f a0 (Stream step s) = go a0 s where
    go a s = a `seq`
                case step s of
                    Yield b s' -> go (f a b) s'
                    Skip    s' -> go a       s'
                    Done       -> a

и это дает обычные функции, подобные списку lengthS = foldl'S (\n _ -> n+1) 0 и т. Д. Это, конечно, не так элегантно, как Conduit или Pipes, но это просто и быстро. Пока все хорошо.

Теперь давайте попробуем объединить потоки «низкого уровня» в потоки более высокого уровня. Например, если у вас есть поток битов Stream Bool, вы можете декодировать биты, чтобы получить Stream Int, используя какой-нибудь умный кодек. Конечно, всегда можно создать новую пошаговую функцию s -> Step s b из функции step, извлеченной из данной Stream step s. Повторные применения функции step :: s->Step s a приводят к неуклюжим каскадам case (step s) of ..., которые снова и снова обрабатывают три возможности Done, Skip, Yield. В идеале aggregate должно выглядеть так:

aggregate :: Stream a -> M?? -> Stream b
newStream = aggregate oldStream $ do
    a1 <- get    -- a1 :: a
    if a1 == True then doSomething
    else do
        a2 <- get
        -- etc.

M?? - это некоторая монада, которую нужно определить. Давайте попробуем тип Appl s a:

newtype Appl s a = Appl ((s->Step s a) -> s -> Step s a)

Он называется Appl, потому что имеет подпись приложения-функции. Экземпляр монады довольно прост:

instance Monad (Appl s) where
    return a = Appl (\_ s -> Yield a s)
    (Appl ap) >>= f = Appl (\step s ->
        case (ap step s) of
            Done -> Done
            Skip s' -> untilNotSkip step s'
            Yield a' s' -> ap' step s' where Appl ap' = f a'

, где untilNotSkip :: (s->Step s a) -> s -> Step s a - это просто повторное (вложенное) применение step :: (s->Step s a) до тех пор, пока не будет возвращено Done или Yield.

Функция get - это обычное применение функции

get :: Appl s a
get = Appl(\step s -> step s)

Чтобы связать вещи, нужно сделать Functor и Applicative, и тут возникает проблема: Appl s нельзя сделать функтором. Подпись

fmap :: (a->b) -> Appl s a -> Appl s b

и это просто не работает, потому что для создания функции (s->Step s b) -> s -> Step s b) из функции (s->Step s a) -> s -> Step s a) мне понадобится b->a. Я могу оттянуть Appl s b над a->b, но я не могу выдвинуть Appl s a - то есть у меня может быть контравариантный функтор, но не функтор. Это странно. Потоки вполне естественны , но я не вижу связи. цель Appl - превратить шаговую функцию s->Step s a в другую s->Step s b.

Здесь что-то не так, Appl не так "M??". Кто-нибудь может помочь?

Обновление

Как указал Ли-яо Ся, тип должен быть примерно таким:

data Walk a b = forall s. Walk ((s->Step s a) -> s -> Step s b)

И экземпляры Functor, Applicative и Monad будут иметь значение

instance Functor (Step s) where
    fmap f Done        = Done
    fmap f (Skip s)    = Skip s
    fmap f (Yield a s) = Yield (f a) s

instance Functor (Walk a) where
    fmap f (Walk t) = Walk ( \step s -> fmap f (t step s) )

-- default Applicative given a monad instance
ap :: (Monad m) => m (a -> b) -> m a -> m b
ap mf m = do
    f <- mf
    x <- m
    return (f x)

untilNotSkip :: (s->Step s a) -> s -> Step s a
untilNotSkip step s = case step s of
    Done        -> Done
    Skip s'     -> untilNotSkip step s'
    Yield a' s' -> Yield a' s'

instance Monad (Walk a) where
    return a = Walk (\_ s -> Yield a s)
    Walk t >>= f =
        Walk (\step s -> case t (untilNotSkip step) s of
            Done        -> Done
            Skip _      -> error "Internal error."
            Yield b' s' -> case f b' of Walk t' -> t' step s'   -- bad
    )

instance Applicative (Walk a) where
    pure = return
    (<*>) = ap

Однако средство проверки типов не разрешит этот экземпляр монады. В определении >>= s в Walk (\step s -> ... отличается от s' в Yield b' s' -> ..., но оно должно быть таким же. Основная проблема здесь заключается в том, что (>>=) :: Walk a b -> (b->Walk a c) -> Walk a c имеет два независимо всех количественных состояния s, одно в первом аргументе и другое, которое возвращается b->Walk a c. Фактически это так (со злоупотреблением обозначениями) (forall s. Walk s a b) -> (forall s'. b->Walk s' a' c) -> (forall s''. Walk s'' a c), что не имеет смысла ни концептуально, ни для проверки типов. Все три s, s', s'' должны быть одного типа.

Вариант, в котором Walk не является полностью количественным значением за s:

data Walk s a b = Walk ((s->Step s a) -> s -> Step s b)

разрешает правильное определение привязки, но тогда aggregate не будет работать:

-- does not compile
aggregate :: Stream a -> Walk s a b -> Stream b
aggregate (Stream step s) (M t) = Stream (t step) s

Опять же, состояние потока s всегда должно быть одинаковым. Одним из выходов из этого является введение data PreStream s a = PreStream (s -> Step s a) s, но это также не позволяет aggregate :: Stream a -> ?? -> Stream b.

Исходный код находится на github .

1 Ответ

0 голосов
/ 02 июля 2018

Давайте снова посмотрим на Appl, потому что оно кажется почти правильным.

newtype Appl s a = Appl ((s->Step s a) -> s -> Step s a)

Идея состоит в том, чтобы определить преобразователь потока путем преобразования ступенчатой ​​функции "низкого уровня" в функцию "высокого уровня". С этой точки зрения эти две пошаговые функции не должны иметь одинаковый вывод. Например, если мы преобразуем биты в байты, нам нужно (s -> Step s Bit) -> s -> Step s Byte.

Таким образом, лучшим типом будет

newtype Walk s b a = Walk ((s -> Step s b) -> s -> Step s a)
-- A walk is many steps.

Кроме того, поскольку Stream количественно количественно определяет s, вам понадобится некоторое универсальное количественное определение по s в какой-то момент, чтобы использовать Walk, так что вы могли бы также поместить его в определение типа.

newtype Walk b a = Walk (forall s. (s -> Step s b) -> s -> Step s a)
...