哪些语言支持*递归*函数文字/匿名函数?

时间:2008-10-01 05:46:48

标签: recursion language-features anonymous-function function-literal letrec

现在似乎有不少主流语言支持function literals。它们也被称为anonymous functions,但我不在乎它们是否有名称。重要的是函数文字是一个表达式,它产生一个尚未在别处定义的函数,因此例如在C中,&printf不计算。

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

我很好奇哪些语言允许你编写递归的函数文字。维基百科的“anonymous recursion”文章没有提供任何编程示例。

让我们使用递归因子函数作为例子。

以下是我所知道的:

  • JavaScript / ECMAScript可以使用callee执行此操作:

    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的绑定对于表达式是局部的,因此整个表达式实际上是一个匿名函数。

还有其他人吗?

16 个答案:

答案 0 :(得分:16)

大多数语言都使用Y combinator来支持它。这是Python中的一个例子(来自cookbook):

# 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

答案 1 :(得分:7)

<强> C#

阅读Wes Dyer's博客,你会发现@Jon Skeet的答案并不完全正确。我不是语言的天才,但是递归匿名函数和“ fib函数真的只是调用本地变量fib引用的委托”引用博客之间存在差异。

实际的C#答案看起来像这样:

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();
} 

答案 2 :(得分:5)

您可以在Perl中执行此操作:

my $factorial = do {
  my $fac;
  $fac = sub {
    my $n = shift;
    if ($n < 2) { 1 } else { $n * $fac->($n-1) }
  };
};

print $factorial->(4);

do块不是绝对必要的;我把它包括在内以强调结果是一个真正的匿名函数。

答案 3 :(得分:4)

嗯,除了你已经提到的Common Lisp(labels)和Scheme(letrec)之外,JavaScript还允许你命名一个匿名函数:

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

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

答案 4 :(得分:4)

在Perl 6中:

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

答案 5 :(得分:2)

F#有“让rec”

答案 6 :(得分:2)

你在这里混淆了一些术语,函数文字不一定是匿名的。

在javascript中,差异取决于函数是作为语句还是表达式编写。对this question的答案中的区别进行了一些讨论。

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

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

这也可以写成:

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

在这两种情况下,它都是函数文字。但请注意,在第二个示例中,名称未添加到周围范围 - 这可能会造成混淆。但这并没有被广泛使用,因为一些javascript实现不支持这个或有一个错误的实现。我也读到它的速度较慢。

匿名递归又有所不同,当一个函数在没有引用自身的情况下进行递归时,已经提到了Y Combinator。在大多数语言中,没有必要使用更好的方法。这是a javascript implementation的链接。

答案 7 :(得分:1)

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

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

我听说有传言说C#团队正在考虑修改明确赋值的规则,以便不需要单独的声明/初始化,但我不会发誓。

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

编辑:正如FryHard指出的那样,这只是伪递归。简单到足以完成工作,但Y-combinator是一种更纯粹的方法。我在上面发布的代码还有另一个警告:如果更改fac的值,那么尝试使用旧值的任何内容都将开始失败,因为lambda表达式已捕获fac变量本身。 (当然,为了正常工作,它必须......)

答案 8 :(得分:1)

您可以在Matlab中使用匿名函数执行此操作,该函数使用dbstack()内省来获取自身的函数文字,然后对其进行评估。 (我承认这是作弊,因为dbstack应该被认为是超语言的,但它可以在所有Matlabs中使用。)

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

这是一个匿名函数,从x倒计时然后返回1.它不是很有用,因为Matlab缺少?:运算符并且不允许匿名函数中的if-blocks,所以很难构造基本情况/递归步骤形式

你可以通过调用f(-1)来证明它是递归的;它会倒数到无穷大并最终抛出最大递归错误。

>> 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。

>> 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”来保护递归案例不受评估。

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

鉴于此,这是阶乘。

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

你可以不受约束地召唤它。

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

答案 9 :(得分:1)

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

(expression[#0]) &

e.g。一个阶乘:

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

这符合用于引用第i个参数的符号#i,以及脚本是其自己的第0个参数的shell脚本约定。

答案 10 :(得分:0)

我认为这可能不是你想要的,但在Lisp'标签'中可以用来动态声明可以递归调用的函数。

(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

答案 11 :(得分:0)

Delphi包含版本2009的匿名函数。

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

的示例
type
  // method reference
  TProc = reference to procedure(x: Integer);               

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

使用:

var
  proc: TProc;
begin
  // anonymous method
  proc := procedure(a: Integer)
  begin
    Writeln(a);
  end;               

  Call(proc);
  readln
end.

答案 12 :(得分:0)

因为我很好奇,我实际上试图在 MATLAB 中找到一种方法。它可以做到,但它看起来有点Rube-Goldberg-esque:

>> 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

答案 13 :(得分:0)

匿名函数存在于带有lambda的C ++ 0x中,它们可能是递归的,虽然我不确定是匿名的。

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

答案 14 :(得分:0)

'看起来你已经知道匿名函数是错误的,它不只是关于运行时创建,它也是关于范围的。考虑这个Scheme宏:

(define-syntax lambdarec
  (syntax-rules ()
    ((lambdarec (tag . params) . body)
     ((lambda ()
       (define (tag . params) . body)
        tag)))))

这样:

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

评估一个真正的匿名递归因子函数,例如可以像:

一样使用
(let ;no letrec used
    ((factorial (lambdarec (f n) (if (<= n 0) 1 (* n (f (- n 1)))))))
  (factorial 4)) ; ===> 24

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

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

该函数随后从内存中清除,没有范围,因此在此之后:

(f 4)

要么发出错误信号,要么被绑定到以前绑定的任何f。

在Haskell中,实现同样的特殊方法是:

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

差别在于这个函数没有作用域,如果我不使用它,Haskell是Lazy,效果和空行代码一样,它是真正的文字,因为它具有与C代码:

3;

一个字面数字。即使我之后立即使用它也会消失。这是文字函数的意义,而不是运行时本身的创建。

答案 15 :(得分:0)

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

> (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是一种更有效的方法:

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