关于递归:哪些语言支持*递归*函数文本/匿名函数?

Which languages support *recursive* function literals / anonymous functions?

现在,似乎有相当多的主流语言支持函数文本。它们也被称为匿名函数,但我不在乎它们是否有名称。重要的是,函数文字是一个表达式,它生成一个在其他地方还没有定义的函数,例如在C中,&printf不算数。

编辑添加:如果您有一个真正的函数文字表达式,您应该能够将它传递给一个函数f(),或者立即将它应用于一个参数,即(5)

我很好奇哪些语言允许您编写递归的函数文本。维基百科的"匿名递归"文章没有给出任何编程示例。

让我们以递归阶乘函数为例。

以下是我知道的:

  • javascript/ecmascript可以用callee来实现:

    1
    function(n){if (n<2) {return 1;} else {return n * arguments.callee(n-1);}}
  • 使用letrec语言很容易,例如haskell(称为let语言):


    let fac x = if x<2 then 1 else fac (x-1) * x in fac

    在Lisp和Scheme中有等价物。注意,fac的绑定是表达式的局部绑定,因此整个表达式实际上是一个匿名函数。

还有其他的吗?


大多数语言都通过使用Y组合器来支持它。下面是Python中的一个示例(摘自《食谱》):

1
2
3
4
5
6
# Define Y combinator...come on Gudio, put it in functools!
Y = lambda g: (lambda f: g(lambda arg: f(f)(arg))) (lambda f: g(lambda arg: f(f)(arg)))

# Define anonymous recursive factorial function
fac = Y(lambda f: lambda n: (1 if n<2 else n*f(n-1)))
assert fac(7) == 5040


C.*

读到韦斯·戴尔的博客,你会发现@jon skeet的回答并不完全正确。我在语言方面不是天才,但是递归匿名函数和"fib函数实际上只是调用本地变量fib引用的委托"之间有区别。

实际的C答案如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
delegate Func<A, R> Recursive<A, R>(Recursive<A, R> r);

static Func<A, R> Y<A, R>(Func<Func<A, R>, Func<A, R>> f)
{
    Recursive<A, R> rec = r => a => f(r(r))(a);
    return rec(rec);
}

static void Main(string[] args)
{
    Func<int,int> fib = Y<int,int>(f => n => n > 1 ? f(n - 1) + f(n - 2) : n);
    Func<int, int> fact = Y<int, int>(f => n => n > 1 ? n * f(n - 1) : 1);
    Console.WriteLine(fib(6));                          // displays 8
    Console.WriteLine(fact(6));
    Console.ReadLine();
}


您可以用Perl实现:

1
2
3
4
5
6
7
8
9
my $factorial = do {
  my $fac;
  $fac = sub {
    my $n = shift;
    if ($n < 2) { 1 } else { $n * $fac->($n-1) }
  };
};

print $factorial->(4);

do块不是严格必要的;我把它包括进来是为了强调结果是一个真正的匿名函数。


好吧,除了前面提到的通用Lisp(labels和scheme(letrec之外,javascript还允许您命名一个匿名函数:

1
var foo = {"bar": function baz() {return baz() + 1;}};

比使用callee更方便。(这与顶层的function不同;后者也会导致名称出现在全局范围内,而在前一种情况下,名称只出现在函数本身的范围内。)


在Perl 6中:

1
2
my $f = -> $n { if ($n <= 1) {1} else {$n * &?BLOCK($n - 1)} }
$f(42);  # ==> 1405006117752879898543142606244511569936384000000000

f有"let rec"


这里您混合了一些术语,函数文本不必是匿名的。

在JavaScript中,差异取决于函数是作为语句还是表达式写入。在这个问题的答案中有一些关于区别的讨论。

假设您正在将示例传递给函数:

1
foo(function(n){if (n<2) {return 1;} else {return n * arguments.callee(n-1);}});

也可以这样写:

1
foo(function fac(n){if (n<2) {return 1;} else {return n * fac(n-1);}});

在这两种情况下,它都是一个函数文本。但是请注意,在第二个示例中,不会将名称添加到周围的作用域中——这可能会令人困惑。但这并没有被广泛使用,因为一些JavaScript实现不支持这一点,或者有一个错误的实现。我也读到过它慢一些。

匿名递归又是不同的,当一个函数在没有引用自身的情况下递归时,已经提到了Y组合器。在大多数语言中,没有必要这样做,因为有更好的方法可用。这里有一个到JavaScript实现的链接。


您可以使用一个匿名函数在matlab中实现这一点,该函数使用dbstack()自省来获取函数本身的文本,然后对其进行计算。(我承认这是作弊,因为dbstack可能被认为是语言外的,但它在所有matlabs中都可用。)

1
f = @(x) ~x || feval(str2func(getfield(dbstack, 'name')), x-1)

这是一个匿名函数,从x开始倒数,然后返回1。它不是很有用,因为matlab缺少?:和不允许匿名函数内的if块,因此很难构造基本情况/递归步骤表单。

您可以通过调用f(-1)来证明它是递归的;它将倒计时到无穷大,并最终引发最大递归错误。

1
2
3
4
>> f(-1)
??? Maximum recursion limit of 500 reached. Use set(0,'RecursionLimit',N)
to change the limit.  Be aware that exceeding your available stack space can
crash MATLAB and/or your computer.

您可以直接调用匿名函数,而不必将其绑定到任何变量,直接将其传递给feval。

1
2
3
4
5
6
>> feval(@(x) ~x || feval(str2func(getfield(dbstack, 'name')), x-1), -1)
??? Maximum recursion limit of 500 reached. Use set(0,'RecursionLimit',N)
to change the limit.  Be aware that exceeding your available stack space can
crash MATLAB and/or your computer.

Error in ==> create@(x)~x||feval(str2func(getfield(dbstack,'name')),x-1)

为了使它有用,您可以创建一个单独的函数来实现递归步骤逻辑,使用"if"保护递归情况不受计算影响。

1
2
3
4
5
6
7
function out = basecase_or_feval(cond, baseval, fcn, args, accumfcn)
%BASECASE_OR_FEVAL Return base case value, or evaluate next step
if cond
    out = baseval;
else
    out = feval(accumfcn, feval(fcn, args{:}));
end

考虑到这个,这里是阶乘。

1
2
3
4
5
recursive_factorial = @(x) basecase_or_feval(x < 2,...
    1,...
    str2func(getfield(dbstack, 'name')),...
    {x-1},...
    @(z)x*z);

你可以称之为无约束。

1
2
3
>> feval( @(x) basecase_or_feval(x < 2, 1, str2func(getfield(dbstack, 'name')), {x-1}, @(z)x*z), 5)
ans =
   120


在C中,您需要声明一个变量来保存委托,并为其分配空值以确保它是明确分配的,然后您可以从分配给它的lambda表达式中调用它:

1
2
3
Func<int, int> fac = null;
fac = n => n < 2 ? 1 : n * fac(n-1);
Console.WriteLine(fac(7));

我想我听到传言说C小组正在考虑改变关于明确分配的规则,使单独的声明/初始化不必要,但我不会发誓。

对于每种语言/运行时环境,一个重要的问题是它们是否支持尾调用。在C中,据我所知,MS编译器不使用tail.IL操作码,但在某些情况下,JIT可能会优化它。显然,这很容易使工作程序和堆栈溢出之间产生差异。(更好地控制这一点和/或保证何时发生。否则,在一台机器上工作的程序可能以难以理解的方式在另一台机器上失败。)

编辑:正如弗莱哈德指出的,这只是伪递归。简单到可以完成任务,但是Y组合器是一种更纯粹的方法。我上面发布的代码还有一个警告:如果更改EDOCX1的值(6),任何试图使用旧值的操作都将开始失败,因为lambda表达式已经捕获了fac变量本身。(当然,为了正常工作,必须这样做…)


Mathematica似乎还允许您使用#0定义递归函数来表示函数本身,如下所示:

1
(expression[#0]) &

例如,阶乘:

1
fac = Piecewise[{{1, #1 == 0}, {#1 * #0[#1 - 1], True}}] &;

这与表示方法#i一致,后者指的是ith参数,而shell脚本约定脚本是它自己的第0个参数。


clojure可以这样做,因为fn为此专门采用了一个可选名称(该名称不超出定义范围):

1
2
3
4
5
6
> (def fac (fn self [n] (if (< n 2) 1 (* n (self (dec n))))))
#'sandbox17083/fac
> (fac 5)
120
> self
java.lang.RuntimeException: Unable to resolve symbol: self in this context

如果恰好是尾部递归,那么recur是一种更有效的方法:

1
2
3
4
> (def fac (fn [n] (loop [count n result 1]
                     (if (zero? count)
                       result
                       (recur (dec count) (* result count))))))


因为我很好奇,我实际上试图在matlab中找到一种方法来实现这一点。它可以做到,但看起来有点像卢布·戈德伯格风格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>> fact = @(val,branchFcns) val*branchFcns{(val <= 1)+1}(val-1,branchFcns);
>> returnOne = @(val,branchFcns) 1;
>> branchFcns = {fact returnOne};
>> fact(4,branchFcns)

ans =

    24

>> fact(5,branchFcns)

ans =

   120


我认为这可能不是您正在寻找的,但在Lisp中,"labels"可以用于动态声明可以递归调用的函数。

1
2
3
4
5
6
7
(labels ((factorial (x) ;define name and params
    ; body of function addrec
    (if (= x 1)
      (return 1)
      (+ (factorial (- x 1))))) ;should not close out labels
  ;call factorial inside labels function
  (factorial 5)) ;this would return 15 from labels

匿名函数用lambda存在于c++ 0x中,它们可能是递归的,尽管我不知道匿名。

1
auto kek = [](){kek();}

"tseems你对匿名函数的想法是错误的,这不仅仅是关于运行时的创建,还涉及范围。考虑此方案宏:

1
2
3
4
5
6
(define-syntax lambdarec
  (syntax-rules ()
    ((lambdarec (tag . params) . body)
     ((lambda ()
       (define (tag . params) . body)
        tag)))))

这样:

1
(lambdarec (f n) (if (<= n 0) 1 (* n (f (- n 1)))))

计算为一个真正的匿名递归阶乘函数,例如,可以使用如下函数:

1
2
3
(let ;no letrec used
    ((factorial (lambdarec (f n) (if (<= n 0) 1 (* n (f (- n 1)))))))
  (factorial 4)) ; ===> 24

然而,让它匿名的真正原因是如果我这样做:

1
((lambdarec (f n) (if (<= n 0) 1 (* n (f (- n 1))))) 4)

该功能随后从内存中清除,没有作用域,因此在此之后:

1
(f 4)

将要么发出错误信号,要么绑定到之前绑定到的任何对象。

在Haskell,实现这一点的一种特别方法是:

1
2
 -> let fac x = if x<2 then 1 else fac (x-1) * x
    in fac n

另一个区别是,这个函数没有作用域,如果我不使用它,haskell是懒惰的,效果和空行代码一样,它确实是字面上的,因为它和C代码的效果一样:

1
3;

字面数字。即使我马上使用它,它也会消失。这就是文字函数的作用,而不是在运行时本身创建。


Delphi包含2009版的匿名函数。

示例来自http://blogs.codegear.com/davidi/2008/07/23/38915/

1
2
3
4
5
6
7
8
type
  // method reference
  TProc = reference to procedure(x: Integer);              

procedure Call(const proc: TProc);
begin
  proc(42);
end;

用途:

1
2
3
4
5
6
7
8
9
10
11
12
var
  proc: TProc;
begin
  // anonymous method
  proc := procedure(a: Integer)
  begin
    Writeln(a);
  end;              

  Call(proc);
  readln
end.