как я могу реализовать этот преобразователь монад с продолжением?

мотивация. Я пытаюсь создать преобразователь монад со специальной инструкцией f <||> g, которая означает «повторить весь этот блок, содержащий f <||> g, один раз с f, в следующий раз с g». Это предназначено для преобразования DSL, хотя вы можете себе представить и другие приложения.

пример использования. Монада computation выражает различные возможные варианты выбора (в данном случае, что нужно напечатать). Функция printme говорит, что делать с каждым разным результатом. В этом случае мы печатаем «начать вычисление» перед его запуском и «---» после.

computation = do
    lift (print "start -- always")
    (lift (print "first choice") <||> lift (print "second choice"))
    lift (print "intermediate -- always")
    (lift (print "third choice") <||> lift (print "fourth choice"))
    lift (print "end -- always")

printme x = do
    putStrLn "=== start computation"
    xv <- x
    putStrLn "---\n"
    return xv

test = runIndep printme computation

вывод выглядит следующим образом:

=== start computation
"start -- always"
"first choice"
"intermediate -- always"
"third choice"
"end -- always"
---

=== start computation
"start -- always"
"first choice"
"intermediate -- always"
"fourth choice"
"end -- always"
---

=== start computation
"start -- always"
"second choice"
"intermediate -- always"
"third choice"
"end -- always"
---

=== start computation
"start -- always"
"second choice"
"intermediate -- always"
"fourth choice"
"end -- always"
---

вопрос. Есть ли чистый способ добиться вышеуказанного поведения, используя какой-то преобразователь монад в стиле продолжения? Я просмотрел статью Олега и др. «Отслеживание с возвратом, чередование и завершение преобразователей монад», но, похоже, не могу полностью понять их реализацию (когда они дошли до реализации msplit с продолжениями).

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

import Control.Monad.Trans.Class

data IndepModelT ???? α = IndepModelT {
    unIndepModelT :: [Bool] -> ???? (α, [Bool]) }

instance Monad ???? => Monad (IndepModelT ????) where
    return x = IndepModelT $ \choices -> return (x, [])
    (IndepModelT x) >>= f = IndepModelT $ \choices -> do
        (xv, branches) <- x choices
        let choices' = drop (length branches) choices
        (fxv, branches') <- unIndepModelT (f xv) choices'
        return (fxv, branches ++ branches')

instance MonadTrans IndepModelT where
    lift x = IndepModelT $ \c -> liftWithChoice [] x
liftWithChoice cs mx = mx >>= \xv -> return (xv, cs)

(<||>)
  :: Monad ???? => IndepModelT ???? α -> IndepModelT ???? α -> IndepModelT ???? α
(IndepModelT f) <||> (IndepModelT g) = IndepModelT go where
    go (False:cs) = do
        (fv, branches) <- f cs
        return (fv, False : branches)
    go (True:cs) = do
        (fv, branches) <- g cs
        return (fv, True : branches)

run_inner next_choices k comp@(IndepModelT comp_inner) = do
    (xv, branches) <- k $ comp_inner next_choices
    case (get_next_choices branches) of
        Nothing -> return ()
        Just choices -> run_inner (choices ++ repeat False) k comp
    where
        get_next_choices [] = Nothing
        get_next_choices [True] = Nothing
        get_next_choices [False] = Just [True]
        get_next_choices (c:cs)
            | Just cs' <- get_next_choices cs = Just $ c:cs'
            | c Prelude.== False = Just [True]
            | otherwise = Nothing

runIndep :: Monad ???? =>
    (???? (α, [Bool]) -> ???? (β, [Bool]))
    -> IndepModelT ???? α
    -> ???? ()
runIndep = run_inner (repeat False)

runIndepFirst (IndepModelT comp) = comp (repeat False)

person gatoatigrado    schedule 04.12.2011    source источник
comment
Вы видели haskell.org/haskellwiki/ListT_done_right и, в частности, альтернативную реализацию haskell.org/haskellwiki/ListT_done_right_alternative?   -  person John L    schedule 05.12.2011
comment
Не думаю, что вам нужны продолжения. Кажется, что у вас есть своего рода дерево, где каждая операция ‹||› представляет собой ветвь. Но я не могу определить правильный тип данных.   -  person Paul Johnson    schedule 05.12.2011


Ответы (3)


Вот в чем проблема: это не монада! Поведение даже не определено четко. F.e. что он должен делать в этом случае:

do
  b <- ...randomly True or False...
  if b then ...some choices... else ...some other choices...

Однако это Applicative. Нам нужен тип [IO a], который представляет собой композицию из двух аппликативных функторов, поэтому мы можем использовать _ 4_ из пакета transformers. Это также дает бесплатно экземпляр Alternative с <|>. Мы будем использовать Rebindable Syntax, чтобы использовать нотацию do для Applicatives:

{-# LANGUAGE RebindableSyntax #-}
import Prelude hiding ((>>), (>>=))
import Control.Applicative
import Data.Functor.Compose

lift :: Applicative f => g a -> Compose f g a
lift = Compose . pure

(>>) :: Applicative f => f a -> f b -> f b
(>>) = (*>)

computation :: Alternative f => Compose f IO ()
computation = do
    lift (print "start -- always")
    lift (print "first choice") <|> lift (print "second choice")
    lift (print "intermediate -- always")
    lift (print "third choice") <|> lift (print "fourth choice")
    lift (print "end -- always")

printme x = do
    putStrLn "=== start computation"
    x
    putStrLn "---\n"

test = mapM printme $ getCompose computation
person Sjoerd Visscher    schedule 06.12.2011

Предложение, которое вы получили до сих пор, не работает. Вот как это будет происходить:

f <||> g = ContT $ \k -> do
  xs <- runContT f k
  ys <- runContT g k
  return $ xs ++ ys

test = runContT computation (return . (:[]))

Но это не перезапускает все вычисления для каждого выбора, результат таков:

"start -- always"
"first choice"
"intermediate -- always"
"third choice"
"end -- always"
"fourth choice"
"end -- always"
"second choice"
"intermediate -- always"
"third choice"
"end -- always"
"fourth choice"
"end -- always"

Пока не нашел хорошего решения.

person Sjoerd Visscher    schedule 05.12.2011
comment
Действительно, спасибо, что указали на это. Предложение @John L о преобразователе ListT done right выглядит лучше для повторного выполнения всех вычислений, но я должен составить тестовую программу. - person acfoltzer; 05.12.2011

Если вы ищете конкретно подход, основанный на продолжении, вы не найдете ничего проще, чем реализация SFKT успешного / неудачного продолжения в LogicT статья.

Если msplit слишком много (а это довольно хитрый зверь), вы можете просто проигнорировать его для этого приложения. Его цель - разрешить справедливое соединение и дизъюнкцию, что не является частью вашей спецификации, если эти строки выходных данных предназначены для печати по порядку. Просто сосредоточьтесь на реализациях Monad и MonadPlus в разделе 5.1, и все будет готово.

Обновление: как указывает Шорд Вишер, это неправильно, поскольку перезапуск происходит только с mplus, а не со всего вычисления. Это гораздо более сложная проблема, чем кажется на первый взгляд.

person acfoltzer    schedule 05.12.2011