我喜欢递归地定义序列,如下所示:
let rec startFrom x =
seq {
yield x;
yield! startFrom (x + 1)
}
Run Code Online (Sandbox Code Playgroud)
我不确定这样的递归序列是否应该在实践中使用.的yield! 出现是尾递归,但我不知道100%的自正在从另一个IEnumerable的内部调用.从我的角度来看,代码在每次调用时都会创建一个IEnumerable实例而不关闭它,这实际上会使这个函数泄漏内存.
这个功能会泄漏内存吗?就此而言,它甚至是"尾递归"?
[编辑添加]:我正在摸着NProf寻找答案,但我认为在SO上获得关于递归序列实现的技术解释是有帮助的.
只是想直截了当.考虑这个Erlang代码的例子:
test() ->
receive
{From, whatever} ->
%% do something
test();
{From, somethingelse} ->
%% do something else
test();
end.
Run Code Online (Sandbox Code Playgroud)
是不是test()调用,只是一个转到?
我问这个是因为在C中我们了解到,如果你进行函数调用,返回位置总是放在堆栈上.我无法想象这里的Erlang一定是这种情况,因为这会导致堆栈溢出.
基本的.我们有两种不同的调用函数的方法:goto和gosub.goto刚刚把程序流引导到其他地方,gosub记得你来自哪里,所以你可以回来.
考虑到这种思维方式,我可以更容易地看一下erlang的递归,因为如果我只读:test()作为goto,那么根本就没有问题.
因此我的问题:不是erlang只是使用goto而不是记住堆栈上的返回地址?
编辑:
只是为了澄清我的观点:
我知道goto可以用在某些语言中来跳过这个地方.但是,只是suupose而不是someFunction()你也可以这样做:在第一个例子中返回流返回的一些函数(),在第二个例子中,流程只在someFunction中继续并且永远不会返回.
所以我们通过跳转到方法起点来限制正常的GOTO行为.
如果你这样看,那么erlang递归函数调用看起来就像一个goto.
(在我看来,goto是一个函数调用,无法返回你来自的地方).这正是erlang示例中正在发生的事情.
可能重复:
递归是否比循环更快?
大约15年前,我第一次接受C语言培训.我的雇主想要高度优化的代码来处理计算困难的任务.我记得不止一次被建议将递归重写为循环,即使是在昂贵的可读性方面,以避免"递归开销".正如我所理解的那样,递归开销是将数据推送到堆栈然后将其弹出所需的额外工作.
现在我用C,Python,Perl编写,有时用Java编写代码,我有时会想到递归.还有什么东西可以通过重写来获得吗?如果它们是尾递归怎么办?现代编译器让所有这些问题都没有实际意义吗?这些担忧是否与解释语言无关?
optimization recursion programming-languages tail-recursion interpreted-language
我正在使用F#规范的最终工作流程的修改版本,以便在Xbox上进行开发.看来,Xbox上的.net框架不支持尾调用.因此,我必须在编译时禁用尾调用优化.
虽然最初似乎这种限制会阻止在计算表达式中使用任何形式的循环,但我最初认为"步进"可以避免这个问题:计算表达式中的递归函数f不直接调用自身,而是返回一个包含lambda的最终值,该lambda调用f.
实验表明我对while循环是正确的(它们在计算表达式中使用时不会导致堆栈溢出),但不是关于递归函数.
为了澄清,这有效:
// Wait until "start" or "A" is pressed on one of the gamepads.
// Set "player" when that happens.
let player : PlayerIndex option ref = ref None
while (!player).IsNone do
for p in all_players do
let state = GamePad.GetState(p)
if state.IsConnected
&& (state.Buttons.Start = ButtonState.Pressed
|| state.Buttons.A = ButtonState.Pressed) then
player := Some p
do! sys.WaitNextFrame()
Run Code Online (Sandbox Code Playgroud)
这会导致堆栈溢出:
// Wait until "start" is pressed on the controlling gamepad.
let rec wait() = task {
input.Update()
if …Run Code Online (Sandbox Code Playgroud) 这是一个使用尾递归的lisp代码.
(defun factorial (f n)
(if (= n 1)
f
(factorial (* f n) (- n 1))))
Run Code Online (Sandbox Code Playgroud)
我把它翻译成clojure代码,期望相同的尾递归优化.
(defn fact [f n]
(if (= n 1)
f
(fact (* f n) (dec n))))
Run Code Online (Sandbox Code Playgroud)
但是我得到了这个整数溢出(不是堆栈溢出),即使是很小的数字,如(fact 1 30).
ArithmeticException integer overflow clojure.lang.Numbers.throwIntOverflow (Numbers.java:1374)
Run Code Online (Sandbox Code Playgroud)
我尝试过recur,但得到了同样的错误.
(defn factorial [f n]
(if (= n 1)
f
(recur (* f n) (dec n))))
Run Code Online (Sandbox Code Playgroud)
clojure代码有什么问题?
我正在玩permutation几个程序,并偶然发现了这个小实验:
置换方法1:
permute([], []).
permute([X|Rest], L) :-
permute(Rest, L1),
select(X, L, L1).
Run Code Online (Sandbox Code Playgroud)
置换方法2:
permute([], []).
permute(L, [P | P1]) :-
select(P, L, L1),
permute(L1, P1).
Run Code Online (Sandbox Code Playgroud)
置换方法3(使用内置):
permute(L, P) :- permutation(L, P).
Run Code Online (Sandbox Code Playgroud)
我知道使用尾递归是一种好习惯,通常使用内置函数应该是有效的.但是当我运行以下内容时:
time(findall(P, permute([1,2,3,4,5,6,7,8,9], P), L)).
Run Code Online (Sandbox Code Playgroud)
我得到了以下结果,这些结果在几次运行中相对一致:
方法1:
% 772,064 inferences, 1.112 CPU in 2.378 seconds (47% CPU, 694451 Lips)
Run Code Online (Sandbox Code Playgroud)
方法2:
% 3,322,118 inferences, 2.126 CPU in 4.660 seconds (46% CPU, 1562923 Lips)
Run Code Online (Sandbox Code Playgroud)
方法3:
% 2,959,245 inferences, 1.967 CPU in 4.217 seconds (47% CPU, 1504539 Lips)
Run Code Online (Sandbox Code Playgroud)
因此,非尾递归方法非常实时有效.
一个特定的递归类型通常更实时有效,所有其他条件相同(我知道这并不总是一个简单的前提)?这个实验告诉我的是,我可能不想总是努力进行尾递归,但我可能需要首先进行性能分析,然后权衡性能优势与尾递归确实具有的其他好处.
我在scala中创建了一个自定义对象树,我的insert方法抛出了一个堆栈溢出,因为它不是尾递归的.但是,我无法弄清楚如何使其尾递归.相关的例子我见过使用"累加器"变量,但是它们或者像Integers这样的东西可以被乘法和覆盖,或者我无法适应树的列表.这就是我所拥有的:
我的树木的基础:
abstract class GeoTree
case object EmptyTree extends GeoTree
case class Node(elem:GeoNode, left:GeoTree, right:GeoTree) extends GeoTree
Run Code Online (Sandbox Code Playgroud)
用于递归创建树的insert方法(导致堆栈溢出的方法):
def insert(t:GeoTree, v: GeoNode): GeoTree = t match {
case EmptyTree => new Node(v, EmptyTree, EmptyTree)
case Node(elem:GeoNode, left:GeoTree, right:GeoTree) => {
if (v < elem) new Node(elem, insert(left, v), right)
else new Node(elem, left, insert(right, v))
}
}
Run Code Online (Sandbox Code Playgroud)
我不认为它的代码GeoNode实际上特别相关,因为它非常简单.这个类有两个Long属性和<,>以及==适当的树中使用重写运营商.有人可以提出如何使用累加器为我的insert功能,或其他一些方法使其尾递归?
首先,我有两个不同的实现,我认为是正确的,并且已经对它们进行了描述并认为它们具有相同的性能:
depth::Tree a -> Int
depth Empty = 0
depth (Branch b l r) = 1 + max (depth l) (depth r)
depthTailRec::Tree a -> Int
depthTailRec = depthTR 0 where
depthTR d Empty = d
depthTR d (Branch b l r) = let dl = depthTR (d+1) l; dr = depthTR (d+1) r in max dl dr
Run Code Online (Sandbox Code Playgroud)
我只是想知道是不是人们都在谈论尾部递归如何有利于性能?很多问题都在我脑海中浮现:
我向所有人道歉,因为以前版本的这个模糊不清.有人决定对这个新女孩表示同情并帮我改写这个问题 - 这是一个我希望能够解决问题的更新(并且,感谢迄今为止所有那些慷慨解答的人):
问题
我是Uni的第一年,我是一名新的计算机科学专业的学生.对于我的算法类的最终项目,我们可以选择我们想要的任何语言,并实现一种"细化"/"效率"算法,该算法在本地(内部?)用另一种语言找到,但在我们选择的语言中缺失.
我们刚刚在课堂上研究了递归,我的教授简要地提到JavaScript没有实现Tail Recursion.从我的在线研究中,新的ECMA脚本6规范包含此功能,但它目前不在任何(/大多数?)JavaScript版本/引擎中?(对不起,如果我不确定哪个是......我是新来的).
我的任务是为缺少的功能提供2个(编码)WORK AROUND的选项.
所以,我的问题是......是否有人,比我更聪明,更有经验,对我如何实现以下方面有任何想法或例子:
解决了缺乏尾递归优化?
我有这个名为Desync的monad -
[<AutoOpen>]
module DesyncModule =
/// The Desync monad. Allows the user to define in a sequential style an operation that spans
/// across a bounded number of events. Span is bounded because I've yet to figure out how to
/// make Desync implementation tail-recursive (see note about unbounded recursion in bind). And
/// frankly, I'm not sure if there is a tail-recursive implementation of it...
type [<NoComparison; NoEquality>] Desync<'e, 's, 'a> =
Desync of ('s -> 's * …Run Code Online (Sandbox Code Playgroud) tail-recursion ×10
recursion ×5
f# ×3
binary-tree ×2
optimization ×2
algorithm ×1
clojure ×1
erlang ×1
goto ×1
haskell ×1
javascript ×1
list ×1
memory-leaks ×1
monads ×1
performance ×1
permutation ×1
prolog ×1
scala ×1
sequence ×1
tree ×1