我可以找到Stream Fusion的唯一资源是介绍它的论文,这些资源并不是最好的学习资源。流融合究竟是如何工作的?
更具体地说,因为这是paper无法解释的部分:在list->流转换(即maps f . maps g
)之后生成的共同结构本身是如何折叠的?
答案 0 :(得分:21)
以下是来自Duncan Coutt's thesis的maps
定义(第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和其他优化。