2012-04-19 2 views
60

Монады могут делать много удивительных, сумасшедших вещей. Они могут создавать переменные, которые содержат суперпозицию значений. Они могут позволить вам получить доступ к данным из будущего, прежде чем вы его вычислите. Они могут позволить вам писать деструктивные обновления, но не совсем. И тогда продолжение монады позволяет вам сломать умы людей! Усушливо ваше собственное. ;-)Монастырь Пауза

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

 
data Pause s x 
instance Monad (Pause s) 
mutate :: (s -> s) -> Pause s() 
yield :: Pause s() 
step :: s -> Pause s() -> (s, Maybe (Pause s())) 

Pause монада является своего рода государственной монады (отсюда mutate, с очевидными семантики). Обычно такая монада имеет какую-то функцию «run», которая запускает вычисление и возвращает вам окончательное состояние. Но Pause отличается: он обеспечивает функцию step, которая выполняет вычисления до тех пор, пока не назовет магическую функцию yield. Здесь вычисление приостанавливается, возвращая вызывающему абоненту достаточную информацию, чтобы возобновить вычисление позже.

Для дополнительной уверенности. Позвольте вызывающему абоненту изменять состояние между звонками step. (Подписи типа выше должны позволить это, например.)


использования: Это часто легко писать код, который делает что-то сложное, но в общей сложности PITA превратить его также выход промежуточные состояния в его работе. Если вы хотите, чтобы пользователь мог изменить что-то в середине исполнения, все становится сложным очень быстро.

идеи реализации:

  • Очевидно это можно сделать с помощью нитей, замков и IO. Но можем ли мы сделать лучше? ;-)

  • Что-то безумное с продолжением монады?

  • Может быть, какая-то писательская монада, где yield просто записывает текущее состояние, а затем мы можем «притворяться» на step, итерируя по состояниям в журнале. (Очевидно, это исключает изменение состояния между этапами, поскольку мы сейчас не «останавливаем» что-либо.)

+3

Нет более безумен, чем любой другой 'Cont' Например, я думаю; ткнуть в 'callCC'. – geekosaur

+2

В первом случае я попытался бы построить свободную монаду на сигнатуре {mutate :: (s -> s) ->(); yield ::() ->()}. – pigworker

+2

У GHC была монада, которую вы могли бы _resume_ (ResumeT), но по какой-то причине она исчезла около версии 6.8. Думаю. –

ответ

53

Конечно; Вы просто дайте любое вычисление либо закончить с результатом, или приостановить себя, давая действие, которое будет использоваться на резюме, наряду с государством в то время:

data Pause s a = Pause { runPause :: s -> (PauseResult s a, s) } 

data PauseResult s a 
    = Done a 
    | Suspend (Pause s a) 

instance Monad (Pause s) where 
    return a = Pause (\s -> (Done a, s)) 
    m >>= k = Pause $ \s -> 
     case runPause m s of 
      (Done a, s') -> runPause (k a) s' 
      (Suspend m', s') -> (Suspend (m' >>= k), s') 

get :: Pause s s 
get = Pause (\s -> (Done s, s)) 

put :: s -> Pause s() 
put s = Pause (\_ -> (Done(), s)) 

yield :: Pause s() 
yield = Pause (\s -> (Suspend (return()), s)) 

step :: Pause s() -> s -> (Maybe (Pause s()), s) 
step m s = 
    case runPause m s of 
     (Done _, s') -> (Nothing, s') 
     (Suspend m', s') -> (Just m', s') 

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

+0

Баллы за включение 'get' и' put' и, следовательно, выполнение духа, а также письмо оригинального вопроса :) –

+0

. Из этой реализации легко понять, что 'step' можно было бы усилить подписи типа' Pause sx -> s -> (либо x (Пауза sx), s) '. Просто измените строку '(Done x, s ') -> (Left x, s')' и измените 'Just' на' Right' в следующей строке. Действительно, ParseResult s a === Либо a (Пауза s) –

+0

Очень красиво сделано. Я забыл, что мне нужно «получить». (Мне действительно не нужно 'put', только' mutate', но это легко.) Это очень хороший ответ. – MathematicalOrchid

8
{-# LANGUAGE TupleSections #-} 
newtype Pause s x = Pause (s -> (s, Either x (Pause s x))) 

instance Monad (Pause s) where 
    return x = Pause (, Left x) 

    Pause k >>= f = Pause $ \s -> let (s', v) = k s in 
           case v of 
            Left x -> step (f x) s' 
            Right x -> (s', Right (x >>= f)) 

mutate :: (s -> s) -> Pause s() 
mutate f = Pause (\s -> (f s, Left())) 

yield :: Pause s() 
yield = Pause (, Right (return())) 

step :: Pause s x -> s -> (s, Either x (Pause s x)) 
step (Pause x) = x 

Вот как я написал бы это. Я дал step немного более общее определение, оно может быть также названо runPause. На самом деле мышление о типе step приводит меня к определению Pause.

В пакете monad-coroutine вы найдете общий трансформатор монады. Монарда Pause s такая же, как Coroutine (State s) Id. Вы можете комбинировать сопрограммы с другими монадами.

Связанные: Запрашивать монада в http://themonadreader.files.wordpress.com/2010/01/issue15.pdf

+0

Код невелик, но +1 для справки Promad monad. – MathematicalOrchid

60

Примечания: вы не предусмотрены себе никакого прямого доступа к текущему состоянию s в этой монаде.

Pause s - это бесплатная монада над mutate и yield операций. Реализовано непосредственно вы получите:

data Pause s a 
    = Return a 
    | Mutate (s -> s) (Pause s a) 
    | Yield (Pause s a) 

instance Monad (Pause s) where 
    return = Return 
    Return a >>= k = k a 
    Mutate f p >>= k = Mutate f (p >>= k) 
    Yield p >>= k = Yield (p >>= k) 

с парой умных конструкторов, чтобы дать вам желаемый API:

mutate :: (s -> s) -> Pause s() 
mutate f = Mutate f (return()) 

yield :: Pause s() 
yield = Yield (return()) 

и ступенчатую функцию, чтобы управлять его

step :: s -> Pause s() -> (s, Maybe (Pause s())) 
step s (Mutate f k) = step (f s) k 
step s (Return()) = (s, Nothing) 
step s (Yield k) = (s, Just k) 

Можно также определить это непосредственно с использованием

data Free f a = Pure a | Free (f (Free f a)) 

(от моего «свободного» пакета) с

data Op s a = Mutate (s -> s) a | Yield a 

тогда у нас уже есть монады для Pause

type Pause s = Free (Op s) 

и просто нужно определить умные конструкторы и степпер.

Изготовление его быстрее.

Теперь эти реализации легко рассуждать, но у них нет самой быстрой операционной модели. В частности, левые ассоциированные применения (>> =) дают асимптотически более медленный код.

Чтобы обойти это, вы можете применить монаду Codensity к существующей бесплатной монаде или просто использовать монаду 'Church free', которую я подробно описываю в своем блоге.

http://comonad.com/reader/2011/free-monads-for-less/

http://comonad.com/reader/2011/free-monads-for-less-2/

http://comonad.com/reader/2011/free-monads-for-less-3/

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

+1

В этом ответе есть много удивительности. Где я могу больше узнать о свободных монадах? (Я знаю, что есть вещи в «типах данных à la carte», но я ищу больше) –

+0

Очень приятно. Хотя мне больше всего понравилось. Я проведу ссылки на блоги позже ... – MathematicalOrchid

+1

http://www.haskell.org/haskellwiki/Free_structure вводит бесплатные монады достаточно хорошо. Я тоже много рассказываю о них в своем блоге, но соответствующий контент там более размыт. –

31

Вот как я мог бы это сделать, используя бесплатно Монады. Э-э, какие они? Это деревья с действиями в узлах и значениями на листьях, с >>=, действующими как прививка деревьев.

data f :^* x 
    = Ret x 
    | Do (f (f :^* x)) 

Это не редкость, чтобы писать F * X для такой вещи в математике, поэтому мое капризное имя типа инфикса.Чтобы сделать экземпляр, вам нужно всего лишь f, чтобы быть чем-то, что вы можете перечислить: любые Functor будут делать.

instance Functor f => Monad ((:^*) f) where 
    return = Ret 
    Ret x >>= k = k x 
    Do ffx >>= k = Do (fmap (>>= k) ffx) 

Это просто «применить k на всех листьях и трансплантат в результате деревьев». Эти деревья могут представлять собой стратегии для интерактивных вычислений: все дерево покрывает все возможные взаимодействия с окружающей средой, а среда выбирает, какой путь в дереве следовать. Почему они бесплатно? Это просто деревья, без которых нет какой-либо интересной теории равенства, говоря, какие стратегии эквивалентны другим стратегиям.

Теперь давайте создадим комплект для создания Функторов, который соответствует набору команд, которые мы, возможно, захотим сделать. Эта вещь

data (:>>:) s t x = s :? (t -> x) 

instance Functor (s :>>: t) where 
    fmap k (s :? f) = s :? (k . f) 

фиксирует идею получения значения в x после один команды с входным типом s и выходным типом t. Для этого вам нужно выбрать ввод в s и объяснить, как продолжить значение в x с учетом вывода команды в t. Чтобы отобразить функцию через такую ​​вещь, вы привязываете ее к продолжению. Пока что стандартное оборудование. Для нашей задачи, мы можем теперь определить два функтора:

type Modify s = (s -> s) :>>:() 
type Yield  =() :>>:() 

Это как я только что записал типы значений для команд, которые мы хотим, чтобы быть в состоянии сделать!

Теперь давайте посмотрим, что мы можем предложить выбор между этими командами. Мы можем показать, что выбор между функторами дает функтор. Более стандартное оборудование.

data (:+:) f g x = L (f x) | R (g x) 

instance (Functor f, Functor g) => Functor (f :+: g) where 
    fmap k (L fx) = L (fmap k fx) 
    fmap k (R gx) = R (fmap k gx) 

Так, Modify s :+: Yield представляет выбор между модифицирования и плодоношения. Любая подпись простых команд (обмениваясь с миром в терминах значений, а не манипулируя вычислениями) может быть превращена в функтор таким образом. Меня беспокоит, что я должен это делать вручную!

Это дает мне вашу монаду: свободную монаду над сигнатурой изменения и урона.

type Pause s = (:^*) (Modify s :+: Yield) 

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

mutate :: (s -> s) -> Pause s() 
mutate f = Do (L (f :? Ret)) 

yield :: Pause s() 
yield = Do (R (() :? Ret)) 

step функция затем дает значение к стратегии деревьев. Это оператор , построивший одно вычисление (возможно) из другого.

step :: s -> Pause s() -> (s, Maybe (Pause s())) 
step s (Ret())   = (s, Nothing) 
step s (Do (L (f :? k))) = step (f s) (k()) 
step s (Do (R (() :? k))) = (s, Just (k())) 

step функция запускает стратегию, пока либо он не закончит с Ret, или она дает, мутирует состояние, как она идет.

Общий метод выглядит следующим образом: отделить команды (взаимодействующую с точкой зрения ценностей) от операторов управления (манипулирование) численных расчётов; построить свободную монаду «деревьев стратегии» над сигнатурой команд (раскручивание дескриптора); реализовать управляющие операторы путем рекурсии над деревьями стратегии.

+3

Награда за самый абстрактный ответ идет ... – luqui

+2

Я думал, что это может быть полезно разоблачить шаблон и создать комплект, который позволяет вам просто создать экземпляр. Конечно, довольно неприятно работать с (Do (L (f:? K))). Это то, что я обычно делаю более понятным с помощью «синонимов шаблонов». Следуя этой схеме (или ее более быстрой разработке), должно быть меньше работы. Возможно, я это сделаю. – pigworker

+1

Определенно самый абстрактный. Лично у меня были проблемы с этим, но я уверен, что кто-то найдет его увлекательным. – MathematicalOrchid

11

Не соответствует вашей подписи типа точно, но, конечно, просто:

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-} 
import Control.Monad.State 

newtype ContinuableT m a = Continuable { runContinuable :: m (Either a (ContinuableT m a)) } 
instance Monad m => Monad (ContinuableT m) where 
    return = Continuable . return . Left 
    Continuable m >>= f = Continuable $ do 
     v <- m 
     case v of 
      Left a -> runContinuable (f a) 
      Right b -> return (Right (b >>= f)) 

instance MonadTrans ContinuableT where 
    lift m = Continuable (liftM Left m) 

instance MonadState s m => MonadState s (ContinuableT m) where 
    get = lift get 
    put = lift . put 

yield :: Monad m => ContinuableT m a -> ContinuableT m a 
yield = Continuable . return . Right 

step :: ContinuableT (State s) a -> s -> (Either a (ContinuableT (State s) a), s) 
step = runState . runContinuable 

-- mutate unnecessary, just use modify 
8

Примечания: Этот ответ available как грамотный файл Haskell в Gist.

Мне очень понравилось это упражнение. Я пытался это сделать, не глядя на ответы, и это того стоило. Мне потребовалось немало времени, но результат на удивление близок к двум другим ответам, а также к библиотеке monad-coroutine. Поэтому я предполагаю, что это довольно естественное решение этой проблемы. Без этого упражнения я бы не понял, как работает monad-coroutine.

Чтобы добавить дополнительную ценность, я объясню шаги, которые в конечном итоге привели меня к решению.

Признавая состояние монады

Поскольку мы имеем дело с состояниями, то мы ищем модели, которые могут быть эффективно описаны государственной монады. В частности, s - s изоморфен s -> (s,()), поэтому его можно заменить на State s(). А функцию типа s -> x -> (s, y) можно перевернуть до x -> (s -> (s, y)), что на самом деле x -> State s y. Это приводит нас к обновленным сигнатурам

mutate :: State s() - Pause s() 
step :: Pause s() - State s (Maybe (Pause s())) 

обобщение

Нашей Pause монады в настоящее время параметризованное государства. Однако теперь мы видим, что нам действительно не нужно государство ни для чего, и мы не используем каких-либо особенностей государственной монады. Таким образом, мы могли бы попытаться сделать более общее решение, которое параметризованное любой монады:

mutate :: (Monad m) = m() -> Pause m() 
yield :: (Monad m) = Pause m() 
step :: (Monad m) = Pause m() -> m (Maybe (Pause m())) 

Кроме того, мы могли бы попытаться сделать mutate и step более общим, позволяя любые значения, а не только (). И, понимая, что Maybe a изоморфна Either a() мы можем, наконец, обобщать свои подписи в

mutate :: (Monad m) = m a -> Pause m a 
yield :: (Monad m) = Pause m() 
step :: (Monad m) = Pause m a -> m (Either (Pause m a) a) 

так, что step возвращает промежуточное значение вычислений.

Монада трансформатор

Теперь мы видим, что мы на самом деле пытаемся сделать монады от монады - добавить некоторые дополнительные функциональные возможности. Это то, что обычно называют monad transformer. Кроме того, подпись mutate точно такая же, как у lift от MonadTrans. Скорее всего, мы на верном пути.

Окончательный монада

step функция, как представляется, наиболее важной частью нашей монады, он определяет только то, что нам нужно. Возможно, это может быть новая структура данных? Давайте попробуем:

import Control.Monad 
import Control.Monad.Cont 
import Control.Monad.State 
import Control.Monad.Trans 

data Pause m a 
    = Pause { step :: m (Either (Pause m a) a) } 

Если Either часть Right, это просто монадическое значение, без каких-либо суспензий. Это приводит нас, как реализовать Самую лёгкую вещь - lift функцию от MonadTrans:

instance MonadTrans Pause where 
    lift k = Pause (liftM Right k) 

и mutate просто специализация:

mutate :: (Monad m) => m() -> Pause m() 
mutate = lift 

Если Either части Left, она представляет собой продолжение вычислений после приостановки. Итак, давайте создадим функцию для этого:

suspend :: (Monad m) => Pause m a -> Pause m a 
suspend = Pause . return . Left 

Теперь yield ИНГ вычисление просто, мы просто приостановить с пустым вычисления:

yield :: (Monad m) => Pause m() 
yield = suspend (return()) 

Тем не менее, мы упускаем самую важную роль. Пример Monad. Давайте исправим его . Реализация return проста, мы просто поднимаем внутреннюю монаду. Реализация >>= немного сложнее. Если исходное значение Pause было только простым значением (Right y), тогда мы просто завершим f y. Если это приостановленное вычисление, которое можно продолжить (Left p), мы рекурсивно спустимся в него.

instance (Monad m) => Monad (Pause m) where 
    return x = lift (return x) -- Pause (return (Right x)) 
    (Pause s) >>= f 
     = Pause $ s >>= \x -> case x of 
      Right y  -> step (f y) 
      Left p  -> return (Left (p >>= f)) 

Тестирование

Давайте попробуем сделать некоторые функции модели, которая использует и обновляет состояние, получая в то время как внутри вычисления:

test1 :: Int -> Pause (State Int) Int 
test1 y = do 
      x <- lift get 
      lift $ put (x * 2) 
      yield 
      return (y + x) 

И вспомогательная функция, которая отлаживает монады - печатает свои промежуточные шаги до пульт:

debug :: Show s => s -> Pause (State s) a -> IO (s, a) 
debug s p = case runState (step p) s of 
       (Left next, s')  -> print s' >> debug s' next 
       (Right r, s')  -> return (s', r)  

main :: IO() 
main = do 
    debug 1000 (test1 1 >>= test1 >>= test1) >>= print 

В результате

2000 
4000 
8000 
(8000,7001) 

, как и ожидалось.

Сопрограммы и монада-сопрограммная

Что мы реализовали это довольно общее монадическим решение, которое реализует Coroutines. Возможно, неудивительно, что у кого-то была идея раньше :-), и создал пакет monad-coroutine. Менее удивительно, это очень похоже на то, что мы создали.

Пакет обобщает эту идею еще дальше. Продолжающееся вычисление хранится внутри произвольного функтора. Это позволяет suspend много вариантов работы с приостановленными вычислениями. Например, чтобы pass a value к вызывающему resume (который мы назвали step), или к wait for a value быть предусмотрено, чтобы продолжить и т.д.

Смежные вопросы