Stream Fusion的工作原理是什么?

时间:2014-01-04 16:53:53

标签: haskell functional-programming

我可以找到Stream Fusion的唯一资源是介绍它的论文,这些资源并不是最好的学习资源。流融合究竟是如何工作的?

更具体地说,因为这是paper无法解释的部分:在list->流转换(即maps f . maps g)之后生成的共同结构本身是如何折叠的?

2 个答案:

答案 0 :(得分:21)

以下是来自Duncan Coutt's thesismaps定义(第1.4.2节):

maps :: (a → b) → Stream a → Stream b
maps f (Stream next0 s0) = Stream next s0
    where
        next s = case next0 s of
            Done → Done
            Skip s′ → Skip s′
            Yield x s′ → Yield (f x) s′

现在考虑表达式

maps f . maps g

编译器可以内联(.)来获取

\x -> maps f (maps g x)

我们可以从Stream的定义中看出它只有一个构造函数:

data Stream a = ∃ s . Stream (s → Step a s) s

所以之前的结果相当于:

\(Stream next0 s) -> maps f (maps g (Stream next0 s))

内联maps g,可以安全地执行maps是非递归的(这是流融合的关键见解):

\(Stream next0 s) -> maps f (Stream next1 s)
      where
          next1 s = case next0 s of
            Done → Done
            Skip s′ → Skip s′
            Yield x s′ → Yield (g x) s′

内联maps f

\(Stream next0 s) -> Stream next2 s
      where
          next1 s = case next0 s of
            Done → Done
            Skip s′ → Skip s′
            Yield x s′ → Yield (g x) s′
          next2 s = case next1 s of
            Done → Done
            Skip s′ → Skip s′
            Yield x s′ → Yield (f x) s′

接下来,我们可以将next1内联到next2并使用“case-of-case”简化case表达式 - 再次注意next1是非递归的 - 并且删除现已死亡的next1

\(Stream next0 s) -> Stream next2 s
      where
          next2 s = case next0 s of
            Done → Done
            Skip s′ → Skip s′
            Yield x s′ → Yield (f (g x)) s′

关键点在于这些步骤都是小优化,这些优化在隔离中是有意义的,并且不需要特殊的编译器知识,无论是流融合本身,还是Stream类型或maps函数。

答案 1 :(得分:3)

你想在call pattern specialization上阅读Peyton Jones的论文,这是流融合库下面的引擎。更进一步的是case-of-case和其他优化。