在Haskell中左递归>> =

时间:2014-06-12 16:59:14

标签: haskell

我刚刚阅读了这篇非常有趣的文章,内容是关于提示 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; =需要二次时间来评估(它与++问题的左递归相同!)

我不明白二次时间(我通过实验检查)的来源。它与懒惰评估有关吗?

有人可以解释我为什么

2 个答案:

答案 0 :(得分:8)

我觉得使用Free monad而不是Prompt更容易解释,尽管它们非常相似。

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

Free monad是由Pure标记的已完成操作或由f标记的Free索引效果。如果fFunctor,那么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))))

我们在这里看到了一些暗示 - 我们必须遍历整个树到根,只是为了什么都不做。

&#34; Tree-Grafting&#34;替补莫纳德

查看Free monad的一种方法是将其视为&#34;替换monad&#34;。它的绑定看起来像

(=<<) :: (a -> Free f b) -> Free f a -> Free f b

如果您认为a -> Free f bPure叶值转换为新的效果树,那么(=<<)只会通过效果树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 + ...),其中aas等的长度,或者只是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表现为(>>=)的直觉,应该清楚为什么左侧关联(++)会导致问题