我刚刚阅读了这篇非常有趣的文章,内容是关于提示 Monad的替代实现:http://joeysmandatory.blogspot.com/2012/06/explaining-prompt-monad-with-simpler.html
以下是可以运行的简化代码:
data Request a where
GetLine :: Request String
PutStrLn :: String -> Request ()
data Prompt p r
= forall a. Ask (p a) (a -> Prompt p r)
| Answer r
instance Monad (Prompt p) where
return = Answer
Ask req cont >>= k = Ask req (\ans -> cont ans >>= k)
Answer x >>= k = k x
prompt :: p a -> Prompt p a
prompt req = Ask req Answer
runPromptM :: Monad m => (forall a. p a -> m a) -> Prompt p r -> m r
runPromptM perform (Ask req cont) = perform req
>>= runPromptM perform . cont
runPromptM _ (Answer r) = return r
handleIO :: Request a -> IO a
handleIO GetLine = return ""
handleIO (PutStrLn s) = putStrLn s
req :: Prompt Request ()
req = do
answers <- sequence $ replicate 20000 (prompt GetLine)
prompt $ PutStrLn (concat answers)
main = runPromptM handleIO req
文章中的评论提到:
它有一个问题,左递归&gt;&gt; =需要二次时间来评估(它与++问题的左递归相同!)
我不明白二次时间(我通过实验检查)的来源。它与懒惰评估有关吗?
有人可以解释我为什么?
答案 0 :(得分:8)
我觉得使用Free
monad而不是Prompt
更容易解释,尽管它们非常相似。
data Free f a = Pure a | Free (f (Free f a)) deriving Functor
Free
monad是由Pure
标记的已完成操作或由f
标记的Free
索引效果。如果f
是Functor
,那么Free f
就是Monad
instance Functor f => Monad (Free f) where
return = Pure
Pure a >>= f = f a
Free m >>= f = Free (fmap (>>= f) m)
此Monad
实例通过&#34;推送&#34;通过Functor
的图层向下绑定,以到达底部的Pure
个节点,然后应用f
。对此重要的是Functor
层的数量不会改变。也就是说,这是Free Maybe ()
中发生的绑定示例。
Free (Just (Free (Just (Free Nothing)))) >>= (const (return ()))
Free (fmap (>>= (const (return ()))) (Just (Free (Just (Free Nothing))))
Free (Just (Free (Just (Free Nothing)) >>= (const (return ())))
Free (Just (Free (fmap (>>= (const (return ()))) (Just (Free Nothing))))
Free (Just (Free (Just (Free Nothing >>= (const (return ()))))))
Free (Just (Free (Just (Free Nothing))))
我们在这里看到了一些暗示 - 我们必须遍历整个树到根,只是为了什么都不做。
查看Free
monad的一种方法是将其视为&#34;替换monad&#34;。它的绑定看起来像
(=<<) :: (a -> Free f b) -> Free f a -> Free f b
如果您认为a -> Free f b
将Pure
叶值转换为新的效果树,那么(=<<)
只会通过效果树Free f a
下降并执行替换a
值。
现在,如果我们有一系列正确关联的绑定(使用Kleisli组合(>=>)
编写,以证明重新关联有效 - Kleisli箭头是一个类别)
f >=> (g >=> h)
然后我们只需要下降到树中一次 - 在树的所有节点上计算替换函数。但是,如果我们与另一个方向相关联
(f >=> g) >=> h
我们得到相同的结果,但必须先计算整个结果 (f >=> g)
,然后才能在h
中应用替换函数。这意味着如果我们有一个深度嵌套的左关联绑定序列
((((f >=> g) >=> h) >=> i) >=> j) >=> k
然后我们不断重新计算左边的结果,以便我们可以对它们进行递归。这是二次减速出现的地方。
这是一个名为Codensity
的奇怪类型,与持续传递风格和ContT
monad有关。
data Codensity m a = Codensity { runCodensity :: forall b . (a -> m b) -> m b }
-- Codensity m a = forall b . ContT b m a
Codensity有一个有趣的特性,即使Functor
不是m
,它也只是instance Functor (Codensity m) where
fmap f m = Codensity (\amb -> runCodensity m (amb . f))
:
Monad
以及m
instance Monad (Codensity m) where
return a = Codensity (\amb -> amb a)
m >>= f = Codensity (\bmc -> runCodensity m (\a -> runCodensity (f a) bmc))
的独特属性,即使Monad
不是:
Codensity
我们还可以往返toCodensity :: Monad m => m a -> Codensity m a
toCodensity ma = Codensity (\amb -> ma >>= amb)
fromCodensity :: Monad m => Codensity m a -> m a
fromCodensity m = runCodensity m return
roundtrip :: Monad m => m a -> m a
roundtrip = fromCodensity . toCodensity
到{{1}}
{{1}}
但是当我们这样做时something very, very interesting happens: all of the binds become right-associated!。
答案 1 :(得分:2)
考虑经典left associated append problem。您可能已经意识到,只要您有一系列左侧关联的类似附加功能(我将在这里使用(++)
),您就会遇到O(n^2)
问题:
(((as ++ bs) ++ cs) ++ ds) ++ ...
很容易看出,每个附加追加都必须遍历前面附加列表的整个长度,导致可怕的慢O(n^2)
算法。
解决方案是合作:
as ++ (bs ++ (cs ++ (ds ++ ...)))
这是O(a + b + c + d + ...)
,其中a
是as
等的长度,或者只是O(n)
在总列表中的长度。
现在,这与Free
有什么关系?我们建议将Free
与[]
的定义进行比较:
data Free f r = Pure r | Free (f (Free f r))
data [] a = [] | Cons a [a]
虽然[]
在每个Cons
节点都有值,但Free
只有Pure
小费的值。除此之外,定义非常相似。 Free
的良好直觉是它是Functor
s的类似列表的数据结构。
现在是Monad
的{{1}}个实例,我们只关心Free
:
(>>=)
请注意,Pure r >>= f = f r
Free x >>= f = Free (fmap (>>= f) x)
遍历(>>=)
的结构,直到达到Free
值,然后将其他Pure
结构移植(附加)到结尾。与Free
非常相似!
凭借(++)
作为Free
列表,Functor
表现为(>>=)
的直觉,应该清楚为什么左侧关联(++)
会导致问题