据我所知 - 如果错误我会在这里纠正 - 良好的Scheme实践是做任何需要循环的事情,重复递归,而且溢出不会成为问题,因为尾部递归是内置的。但是,Lisp没有溢出保护,因此,所有的循环迭代宏(loop
,while
等)都没有。因此在现实世界的Lisp中,你通常不使用递归,而Scheme只需要它。
如果我的假设是正确的,有没有办法用Lisp“纯粹”而不是冒险溢出?或者这对游戏来说太过游泳以在Lisp中使用递归?我从The Little Schemer回忆起他们如何通过递归给你一个彻底的锻炼。还有一个名为The Little Lisper的早期版本。它是否在Lisp中为您提供相同的递归训练?然后Land of Lisp让我对循环或递归是否是“最佳实践”感到困惑。
我要做的是决定是否在Emacs组织模式中使用Racket,或者只为初学者使用内置的Elisp。我希望学生保持尽可能纯粹的功能,例如,我不想解释递归的新的和困难的话题,然后说“哦,但我们不会使用它......”
据我所知 - 如果错误我会在这里纠正 - 良好的Scheme实践是做任何需要循环的事情,重复递归,而且溢出不会成为问题,因为尾部递归是内置的。
据我所知,这是正确的。
但是,Lisp没有溢出保护[...]
不完全是。大多数自我相应的Common Lisp实现提供尾调用合并(有一些限制,请参阅https://0branch.com/notes/tco-cl.html)。不同之处在于语言规范没有要求。这样可以在实现各种Common Lisp功能时为编译器编写者提供更多自由。 Emacs Lisp没有TCO,除了像recur或tco(自我递归)这样的库。
...因此,所有循环迭代宏(循环,而等)。因此在现实世界的Lisp中,你通常不使用递归,而Scheme只需要它。
差异主要是文化。取一个REPL(Read-Eval-Print-Loop)。我认为将交互视为循环而不是无限尾递归函数更有意义。不知何故,在纯函数式语言中似乎有些不情愿甚至将循环视为原始控制结构,而迭代过程被认为更优雅。为什么不两个?
如果我的假设是正确的,有没有办法用Lisp“纯粹”而不是冒险溢出?或者这对游戏来说太过游泳以在Lisp中使用递归?
你肯定可以在Lisp中使用递归,只要你不滥用它,小程序不应该这样。例如,考虑OCaml中的map
不是尾递归的,但人们仍然经常使用它。如果您使用SBCL,手册中有一节说明如何强制执行尾部呼叫消除。
我要做的是决定是否在Emacs组织模式中使用Racket,或者只为初学者使用内置的Elisp。我希望学生保持尽可能纯粹的功能,例如,我不想解释递归的新的和困难的话题,然后说“哦,但我们不会使用它......”
如果您想教授函数式编程,请使用功能更强大的语言。换句话说,在Racket和Emacs Lisp之间,我会说Racket更适合学生。使用Racket教授功能编程的材料还有很多,还有Typed Racket。
典型的Lisp方言和各种Scheme方言之间存在很多差异:
方案
口齿不清
部分内容适用于Lisp方言,如Emacs Lisp,Common Lisp或ISLisp。
实现通常对堆栈大小有严格的限制,这使得非TCO递归函数成为问题。通常,可以预先设置较大的堆栈大小或在运行时扩展堆栈大小。
尾调用优化与动态范围构造(如特殊变量或类似变量)有一些不兼容性。
有关Common Lisp中的TCO支持,请参阅:Tail Call Optimisation in Common Lisp Implementations
样式
在Racket中,选择的循环结构是for
。
http://docs.racket-lang.org/guide/for.html
Racket(如Scheme)也有TCO,所以原则上你可以使用函数调用编写所有循环结构 - 它不是很方便。
在Emacs Lisp中没有Tail Call Optimization(TCO),所以即使我个人非常喜欢递归,我也建议不要在Elisp中使用它(当然除了它是唯一的自然选择)。
Elisp作为一个编程环境相当不错,但我必须同意“coredump”,并建议使用Racket,这对初学者有很大的支持。