继续传递风格代码表现良好吗?

2021-04-14 09:40:48

延续传递风格是一种强大的技术,允许您摘要在您的程序中进行控制流程。这是一个简单的例子:我们想在一个表中看起来,但有时我们使用的键与任何值都没有。在这种情况下,我们必须做一些不同的事情,但查找代码没有知道来电者想要做什么,并且呼叫者没有知道查找代码如何工作。通常,我们将安排查找代码来返回一个特殊的“未找到键”的值:(让((答案(查询键表))))(如果(eq答案'键 - 未找到)...处理缺少钥匙......计算答案的东西......)

这种方法有两个小问题。首先,“未找到键”值必须在查找返回的类型中。考虑一个只能包含整数的表。不幸的是,我们不能声明回答是一个整数,因为它可能是“未找到键”的值。或者,我们可能决定保留一个特殊的整数来指示“未找到键”。然后可以将答案声明为整数,但现在现在无法存储在表中的魔法整数。无论哪种方式,答案都是可以存储在表中的超级类型,我们必须通过将其测试到“未找到的键”来将其投影回来。

第二个问题是冗余之一。据推测,查找代码中的某个地方有一个条件的凯恩和#39;要找到。我们采取分支并返回“未找到的键”值。但是现在来电者测试返回值对“未找到的键”,它也占用分支。如果在Callee拍摄真正的分支,我们只会在呼叫者中占据真正的分支,如果在Callee中拍摄了假分支,我们只会在呼叫者中拍摄错误分支。从本质上讲,我们正在两次支付完全相同的条件。我们' ve Reedized控制流程,将requed值注入可能的返回值的空间,通过函数调用边界,然后投影并将值反映回呼叫站点的控制流程。

如果我们在延续传递风格中写下这一点,呼叫看起来像这样(Lookup Key表(答案)...计算带有答案的东西......)(lambda()...处理缺少键...))

如果找到,请在答案上调用第一个lambda表达式,但如果答案未排出,则会调用第二个lambda表达式。我们不再有一个特殊的“键找不到”的价值,所以答案可以完全是存储在表中的类型,我们不必保留魔法价值。呼叫者也没有冗余条件测试。

这很酷,但有成本。首先是它需要练习来读取继续传递风格代码。我想它需要练习来阅读任何代码,但有些语言使其在兰姆达表达式周围传递额外繁琐。 (有些人似乎积极敌对这个想法。)它'直接风格会在持续的情况下更加模糊。

第二个成本是性能和效率之一。您传递到延续传递风格计划的Lambda表达式将必须在呼叫者和#39;环境中关闭,这可能意味着存储分配。当Callee调用其中一个持续存在时,它必须执行函数调用。最后,延续中的词汇范围变量必须从关闭和#39;环境中获取。直接样式表现更好,因为它避免了所有词汇封闭机​​械,并且可以在本地堆栈帧中保持变量。由于这些原因,如果需要执行,您可能会在继续传递风格中编写代码的保留。

延续传递风格看起来很复杂,但你不需要一个足够的Smart™编译器来从中生成有效的代码。这里的查找编码为说明:( defun查找(键表If-notel-not-pound of-not-pound)((扫描 - 条目(条目)(cond((null条目)(funcall If-not-found))( (eq(caar条目)键)(Funcall If-powd(CDAR条目))))(t(扫描 - 条目(CDR条目)))))))(扫描 - 条目表)))

和样本使用可能是(Defun探测器(东西)(查找物*特殊表*(lambda(价值)(格式t"〜s"东西值)(lambda( )(格式t"〜s不特殊。"东西)))

通常,探测器必须将两个闭包分配以进入查找,并且每个封闭件中的代码将从封闭件中重新排出键的词汇值。 uTWithout改变查找或探测Wecan(DecrimaIm(Inline Lookup))。显然,内联的Thecall将消除函数调用的开销,但观察Whathappens到闭包:( defun探测器(东西)((lambda(lambda(key表If-notel-not-power)((扫描 - 条目(表)(扫描 - 条目(表)(表)(cond((null条目)(funcall If-not-pound))((eq(caar条目)键)(funcall if-powal(cdar条目)))(t(扫描 - 条目(CDR条目))))) ))(扫描 - 条目表)))的东西*特殊表*(lambda(价值)(格式t" s映射到〜s。"东西值))(lambda()(格式t "〜s没有映射。"东西))))

一个decentcompiler™将很容易注意到关键是一个别名的东西,那表是*特殊表*的别名,所以我们得到:( defun探测器(东西)((lambda(lambda(lambda)(标签((扫描 - 条目(条目)(cond((null条目)(funcall If-not-pound))((eq(caar条目)的东西)(funcall if-power(cdar条目)))(t(t(扫描 - 条目(CDR条目))))))(扫描 - 条目*特殊表*)))(lambda(值)(格式t"〜s映射到〜s。"东西值))( lambda()(格式t"〜s没有映射。"东西)))

和if-notel的表单和if-not-foundare副作用,所以它们可以被联合(并且我们预计thecompiler要正确避免意外的变量捕获):( defun探测器(东西)((lambda()(labda()(扫描 - 条目(条目)(COND((null条目)(funcall(lambda()(格式t"〜s没有映射。"东西)))((eq(caar条目)的东西) (Funcall(Lambda(价值)(格式t"〜s映射到〜s。"东西值))(cdar条目)))(t(扫描 - 条目(CDR条目)))))))) (扫描 - 条目*特殊表*))))))

并且可以删除立即呼叫容托利替兰州lambdas :( defun探测器(东西)(标签((扫描 - 条目)(cond(null条目)(formatt t"〜s没有映射。东西)((eq(caar条目)的东西)(格式t"〜s地图到〜s。"事物(cdar值))))(t(扫描 - 条目(CDR条目))) )))(扫描 - 条目*特殊表*))))

我们的体面编译器™已删除所有词汇封闭机​​械,并将持续呼叫转换为直接代码。此代码具有我们所希望的所有功能:没有特殊的“键未找到”值搞定我们的类型,没有冗余分支:(空条目)测试直接分支到相应的处理代码中,我们不分配闭包而且已经关闭的变量现在在帧中直接显而易见。

它'有点空虚,观察着环中的功能更好。 当然它确实如此。 至少你避免了过程调用。 但是,如果您内联延续的传递风格函数,则任何体面的编译器™都会前往城镇并优化延续开销。 它'一个意想不到的奖金。 有时,我发现继续传递风格只是某些代码的抽象,这也是关键的性能。 我不给它一个第二思想。 如果您只需输入关键呼叫,则继续传递风格可能导致高性能代码。