Какие хорошие обертки для передачи изменения состояния в haskell?

Я пытаюсь реализовать простой бэкэнд FRP в своих интересах.

Я решил использовать чистые функции: так что никакого IO в ядре. Реализация основана на преобразователе сигналов.

Я уже пробовал два способа:

newtype SF a b = SF { listen :: [a] -> [b] }

https://gist.github.com/Heimdell/9675964#file-streamer-hs-L1

а также

newtype SF a b = SF { run :: a -> (b, SF a b) }

https://gist.github.com/Heimdell/9675964#file-behaviour-hs-L1 (ошибочное название, извините)

Оба способа позволяют сделать комбинатор fold/integrate :: (a -> b -> b) -> b -> SF a b для интегрирования сигналов.

У обоих способов есть проблема: кажется невозможным создать действительный экземпляр ArrowApply/Monad.

  • Stream-way: у нас есть список пар (arrow, x) - или, unziped, пара списков (arrows, xs).

    • If we will map head to the result of zipWith ($) 'em, we will loose the carried-along arrow mutation.
    • если мы заставим head arrows слушать xs, мы заморозим состояние первой взятой стрелы.
  • Явный путь состояния:

    instance ArrowApply Behaviour where
        app =
            Behaviour $ \(bf, a) ->
                let (bf1, c) = bf `runBehaviour` a
    
                in (app, c)
    

    Здесь нам нужно как-то корректно внедрить bf1 в возвращаемое app, что невозможно (и на самом деле внедрение (const bf1 *** id) приводит к недопустимому поведению, аналогичному второму из другой реализации.

Есть ли способ сделать SF, который позволяет экземпляр ArrowApply?

P.S.: У потока-пути есть утечка памяти в ArrowChoice, когда ветка долгое время не используется. Пока не могу это исправить. Можно ли когда-нибудь сделать его версию без утечек?

P.P.S: Если нужно время, он мог бы заархивировать его с вводом.


person Heimdell    schedule 27.03.2014    source источник
comment
Для второго типа, newtype SF a b = SF { run :: a -> (b, SF a b) }, вы можете заметить, что экземпляр Monad всегда вызывает событие a как во внутреннем, так и во внешнем контейнерах для каждого события. Следовательно, состояние внутреннего контейнера не будет иметь значения.   -  person Cirdec    schedule 28.03.2014
comment
Вас также может заинтересовать вопрос Дж. Абрахамсона о реализации join в FRP: stackoverflow.com/questions/20890439/behavior-now-in-frp/   -  person Cirdec    schedule 28.03.2014
comment
У меня сложилось впечатление, что стрелки особенно светятся, когда вы просто не можете создать осмысленный экземпляр ArrowApply/Monad.   -  person Xeo    schedule 28.03.2014


Ответы (1)


Я не могу найти ни одного возможного экземпляра, который просто не отбрасывает состояние внутреннего контейнера. Это неудивительно, поскольку возврат чистой функции, привязанной к данным, должен возвращать одно и то же каждый раз при вызове ввода, независимо от того, вызывалась ли она ранее, на что вы намекаете в своих комментариях.

Сами по себе

Единственные Monad экземпляры, которые я могу придумать, отбрасывают последующие состояния для внутреннего контейнера.

instance Monad (SF e) where
    return a = SF . const $ (a, return a)
    (>>=) sa f = SF go
        where
            go e = 
                let
                    (a, sa') = run sa e
                    sb = f a
                    (b, _) = run sb e
                in
                    (b, sa' >>= f)

or

join :: SF e (SF e a) -> SF e a
join ssa = SF go
    where
        go e =
            let
                (sa, ssa') = run ssa e
                (a, _) = run sa e
            in
                (a, join ssa')

Их можно выразить более кратко, используя экземпляр Monad для функций

instance Monad (SF e) where
    return a = SF . const $ (a, return a)
    (>>=) sa f = SF {
        run =
            do
                (a, sa') <- run sa
                (b, _) <- run (f a)
                return (b, sa' >>= f)
    }

Мы можем поискать в другом месте что-то немного другое.

Экземпляр функциональной монады

Ваша newtype SF e a = SF { run :: e -> (a, SF e a) } очень близка к функции от e до a. Для экземпляра монады для функций единственным разумным >>= является передача аргумента как внутренней, так и внешней функциям. Это то, что мы уже придумали. Посмотрим, сможем ли мы придумать что-нибудь еще.

Штат

Ваш код чем-то похож на Преобразователь монады StateT применяется к экземпляру монады для функций. К сожалению, это не дает того, что мы ищем.

Рассмотрим следующее (преобразователь монады StateT):

newtype StateT s m a = StateT { runStateT :: s -> m (a, s)}

Применяется к типу ((->) e) функции, принимающей аргумент `e.

StateT s ((->) e) a имеет единственный конструктор StateT { runStateT :: s -> e -> (a, s) }.

Это отличается от вашего типа тем, что должно быть предоставлено начальное состояние, и состояние отслеживается явно, а не уже включено в возвращаемое следующее значение. Давайте посмотрим, каким будет экземпляр Monad для этого. Экземпляр Monad StateT находится

instance (Monad m) => Monad (StateT s m) where
    return a = state $ \s -> (a, s)
    m >>= k  = StateT $ \s -> do
        ~(a, s') <- runStateT m s
        runStateT (k a) s'

state f = StateT (return . f)

В сочетании с экземпляром для (->) e

instance Monad ((->) e) where
    return = const
    (>>=) x y z = y (x z) z

Мы получим следующее, где do сбрасывает работу на экземпляр для ((->) e)

instance Monad (StateT s ((->) e) where
    return a = StateT (const . (\s -> (a, s)))
    m >>= k  = StateT $ \s e ->
        let (a, s`) = runStateT m s e
        in runStateT (k a) s` e

Это выглядит совсем иначе. Мы не теряем историю ни одного государства. Здесь происходит то, что состояние внутреннего контейнера передается ему из внешнего контейнера, и два контейнера должны иметь один и тот же тип состояния, чтобы это работало. Это совсем не то, чего мы хотим.

Что-то новое

Что произойдет, если мы попытаемся сделать что-то вроде StateT из вашего типа? Мы хотели бы иметь возможность передать тип (->) e и получить структуру, подобную вашей. Мы создадим что-то под названием SFT m a, чтобы SFT ((-> e) a имел ту же структуру, что и SF e a.

newtype SF  e a = SF  { run   :: e -> (a, SF  e a)
newtype SFT m a = SFT { unSFT :: m    (a, SFT m a) }

Мы можем заменить тип, полученный путем применения SFT к (->) e) вместо SF, примененного к e.

SF        e  a -- is replaced by
SFT ((->) e) a

У этого есть один конструктор

SF  { run   :: e -> (a, SF        e  a) }
SFT { unSFT :: e -> (a, SFT ((->) e) a) }

Это не дает нового понимания, единственный Monad пример, который я могу придумать, поскольку он почти идентичен исходному.

instance Monad m => Monad (SFT m) where
    return a = SFT . return $ (a, return a)
    (>>=) sa f = SFT {
        unSFT =
            do
                (a, sa') <- unSFT sa
                (b, _) <- unSFT (f a)
                return (b, sa' >>= f)
    }
person Cirdec    schedule 28.03.2014
comment
Проблема скорее возникает из-за невозможности деконструировать (и реконструировать) объект типа a -> b. Спасибо за ваш ответ. - person Heimdell; 28.03.2014