Scheme和Lisp最佳实践:对于Scheme的递归是,对于Lisp是否为no?

问题描述 投票:4回答:4

据我所知 - 如果错误我会在这里纠正 - 良好的Scheme实践是做任何需要循环的事情,重复递归,而且溢出不会成为问题,因为尾部递归是内置的。但是,Lisp没有溢出保护,因此,所有的循环迭代宏(loopwhile等)都没有。因此在现实世界的Lisp中,你通常不使用递归,而Scheme只需要它。

如果我的假设是正确的,有没有办法用Lisp“纯粹”而不是冒险溢出?或者这对游戏来说太过游泳以在Lisp中使用递归?我从The Little Schemer回忆起他们如何通过递归给你一个彻底的锻炼。还有一个名为The Little Lisper的早期版本。它是否在Lisp中为您提供相同的递归训练?然后Land of Lisp让我对循环或递归是否是“最佳实践”感到困惑。

我要做的是决定是否在Emacs组织模式中使用Racket,或者只为初学者使用内置的Elisp。我希望学生保持尽可能纯粹的功能,例如,我不想解释递归的新的和困难的话题,然后说“哦,但我们不会使用它......”

scheme common-lisp racket elisp
4个回答
6
投票

据我所知 - 如果错误我会在这里纠正 - 良好的Scheme实践是做任何需要循环的事情,重复递归,而且溢出不会成为问题,因为尾部递归是内置的。

据我所知,这是正确的。

但是,Lisp没有溢出保护[...]

不完全是。大多数自我相应的Common Lisp实现提供尾调用合并(有一些限制,请参阅https://0branch.com/notes/tco-cl.html)。不同之处在于语言规范没有要求。这样可以在实现各种Common Lisp功能时为编译器编写者提供更多自由。 Emacs Lisp没有TCO,除了像recurtco(自我递归)这样的库。

...因此,所有循环迭代宏(循环,而等)。因此在现实世界的Lisp中,你通常不使用递归,而Scheme只需要它。

差异主要是文化。取一个REPL(Read-Eval-Print-Loop)。我认为将交互视为循环而不是无限尾递归函数更有意义。不知何故,在纯函数式语言中似乎有些不情愿甚至将循环视为原始控制结构,而迭代过程被认为更优雅。为什么不两个?

如果我的假设是正确的,有没有办法用Lisp“纯粹”而不是冒险溢出?或者这对游戏来说太过游泳以在Lisp中使用递归?

你肯定可以在Lisp中使用递归,只要你不滥用它,小程序不应该这样。例如,考虑OCaml中的map不是尾递归的,但人们仍然经常使用它。如果您使用SBCL,手册中有一节说明如何强制执行尾部呼叫消除。

我要做的是决定是否在Emacs组织模式中使用Racket,或者只为初学者使用内置的Elisp。我希望学生保持尽可能纯粹的功能,例如,我不想解释递归的新的和困难的话题,然后说“哦,但我们不会使用它......”

如果您想教授函数式编程,请使用功能更强大的语言。换句话说,在Racket和Emacs Lisp之间,我会说Racket更适合学生。使用Racket教授功能编程的材料还有很多,还有Typed Racket。


5
投票

典型的Lisp方言和各种Scheme方言之间存在很多差异:

方案

  • 需要尾调用优化
  • 有利于在命令式循环结构上的尾递归
  • 不喜欢势在必行的控制流程
  • 提供功能抽象
  • 尝试将控制结构映射到函数(请参阅例如CALL-WITH-CURRENT-CONTINUATION)
  • 实现一些循环宏作为扩展

口齿不清

部分内容适用于Lisp方言,如Emacs Lisp,Common Lisp或ISLisp。

  • 具有低级控制流,例如GOTO类构造:不要在用户级代码中使用它们,有时它们可​​能在宏中很有用。
  • 没有标准化甚至提供尾部调用优化:为了最大程度的可移植性,您的代码中不需要TCO
  • 提供简单的宏:DO,DOTIMES,DOLIST:在直接适用的地方使用它们
  • 提供复杂的宏:LOOP或ITERATE作为库
  • 提供MAP,MAPCAR,REDUCE等功能抽象

实现通常对堆栈大小有严格的限制,这使得非TCO递归函数成为问题。通常,可以预先设置较大的堆栈大小或在运行时扩展堆栈大小。

尾调用优化与动态范围构造(如特殊变量或类似变量)有一些不兼容性。

有关Common Lisp中的TCO支持,请参阅:Tail Call Optimisation in Common Lisp Implementations

样式

  • 有些人更喜欢递归函数,但我不会一般使用它。通常我(这是我个人的观点)更喜欢显式循环结构而非一般递归调用。其他人更喜欢使用递归函数的更多数学方法。
  • 如果存在像MAP这样的高阶函数,请使用它。
  • 如果循环代码变得更复杂,使用强大的LOOP语句可能很有用

2
投票

在Racket中,选择的循环结构是for

http://docs.racket-lang.org/guide/for.html

Racket(如Scheme)也有TCO,所以原则上你可以使用函数调用编写所有循环结构 - 它不是很方便。


1
投票

在Emacs Lisp中没有Tail Call Optimization(TCO),所以即使我个人非常喜欢递归,我也建议不要在Elisp中使用它(当然除了它是唯一的自然选择)。

Elisp作为一个编程环境相当不错,但我必须同意“coredump”,并建议使用Racket,这对初学者有很大的支持。

© www.soinside.com 2019 - 2024. All rights reserved.