Monads可以做许多惊人的,疯狂的事情。他们可以创建具有值叠加的变量。它们可以允许您在计算之前访问未来的数据。它们可以让您编写破坏性更新,但不是真的。然后延续monad允许你打破人们的思想!通常是你自己的。 ; - )
但这是一个挑战:你能制作一个可以暂停的单子吗?
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
monad是一种状态monad(因此mutate
,具有明显的语义)。通常情况下,像这样的monad具有某种“运行”功能,它运行计算并将您送回最终状态。但是Pause
是不同的:它提供了一个step
函数,它运行计算直到它调用神奇的yield
函数。这里计算暂停,返回给调用者足够的信息以便稍后恢复计算。
对于额外的awesomness:允许调用者修改step
调用之间的状态。 (例如,上面的类型签名应该允许这样做。)
使用案例:编写执行复杂操作的代码通常很容易,但是总的PITA将其转换为输出其操作中的中间状态。如果您希望用户能够在执行过程中更改某些内容,那么事情就会变得非常复杂。
实施思路:
显然可以使用线程,锁和IO
来完成。但我们能做得更好吗? ; - )
继续monad疯狂吗?
也许某种编写器monad,其中yield
只记录当前状态,然后我们可以通过迭代日志中的状态来“伪装”到step
。 (显然这排除了改变步骤之间的状态,因为我们现在并没有真正“暂停”任何事情。)
答案 0 :(得分:62)
注意:您没有直接访问此monad中的当前状态s
。
Pause s
只是mutate
和yield
操作的免费monad。直接实施你得到:
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
的monadtype Pause s = Free (Op s)
只需要定义智能构造函数和步进器。
加快速度。
现在,这些实现很容易推理,但它们没有最快的运营模式。特别是,(>> =)的左关联使用产生渐近较慢的代码。
要解决此问题,您可以将Codensity monad应用于现有的免费monad,或者直接使用'Church free' monad,我在博客中深入介绍了这两个monad。
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/
应用Free monad的Church编码版本的结果是,您可以轻松地推断出数据类型的模型,并且您仍然可以获得快速评估模型。
答案 1 :(得分:55)
不确定;你只是让任何计算结果,或者暂停自己,给出一个在恢复时使用的动作,以及当时的状态:
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
延续,或者在暂停时添加剩余的计算。
答案 2 :(得分:31)
以下是我使用免费 monad进行操作的方法。呃,嗯,他们是什么?它们是在节点处有动作的树和叶子上的值,>>=
就像树木嫁接一样。
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
并在生成的树木中移植”。这些树可以表示用于交互式计算的策略:整个树覆盖与环境的每个可能的交互,并且环境选择树中要遵循的路径。为什么他们免费?他们只是树木,对它们没有有趣的等式理论,说哪些策略等同于其他策略。
现在让我们有一个用于制作Functors的工具包,它们对应于我们可能希望能够执行的一系列命令。这件事
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
表示修改和屈服之间的选择。任何简单命令的签名(以值而不是操纵计算与世界通信)都可以通过这种方式变成仿函数。我不得不亲手做到这一点!
这给了我你的monad:免费monad超过了修改和收益的签名。
type Pause s = (:^*) (Modify s :+: Yield)
我可以将modify和yield命令定义为one-do-then-return。除了协商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 :(得分: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
答案 4 :(得分: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包中你会发现一般的monad变换器。 Pause s
monad与Coroutine (State s) Id
相同。您可以将协同程序与其他monad结合使用。
相关:http://themonadreader.files.wordpress.com/2010/01/issue15.pdf
中的提示单子答案 5 :(得分:8)
注意:这个答案是available作为Gist上的文字Haskell文件。
我非常喜欢这个练习。我试着这样做而没有看到答案,这是值得的。这花了我相当长的时间,但结果令人惊讶地接近其他两个答案,以及monad-coroutine库。所以我想这是解决这个问题的一种自然方式。如果没有这个练习,我就不会理解 monad-coroutine 是如何工作的。
为了增加一些额外的价值,我将解释最终导致我解决问题的步骤。
识别州monad
由于我们正在处理状态,因此我们寻找状态monad可以有效描述的模式。特别是,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
monad目前正由州进行参数化。但是,现在我们看到我们并不需要任何状态,也不会使用状态monad的任何细节。因此,我们可以尝试制作一个由任何monad参数化的更通用的解决方案:
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变压器
现在,我们看到我们实际上正在尝试从monad中创建monad - 添加一些额外的功能。这通常被称为monad transformer。此外,mutate
的签名与MonadTrans
的{{3}}完全相同。最有可能的是,我们正走在正确的轨道上。
最终的monad
step
函数似乎是我们monad中最重要的部分,它定义了我们需要的东西。也许,这可能是新的数据结构?我们试试吧:
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
,那么它只是一个monadic值,没有任何
悬浮液。这导致我们如何实现最简单的事情 - 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)
调试monad的辅助函数 - 将其中间步骤打印到 控制台:
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)
正如所料。
协同程序和 monad-coroutine
我们实施的是一个非常通用的monadic解决方案,它实现了lift。也许并不奇怪,之前有人有这个想法:-),并创建了Coroutines包。不足为奇的是,它与我们创造的非常相似。
该方案进一步概括了这一想法。持续计算存储在任意仿函数中。这允许monad-coroutine许多变体如何处理暂停的计算。例如,suspend向pass a value的来电者(我们称之为step
)或resume提供以继续等等。