Какие языки поддерживают * рекурсивные * функциональные литералы / анонимные функции?
Кажется, в наши дни довольно много основных языков поддерживают функциональные литералы. Их также называют анонимными функциями, но мне все равно, есть ли у них имя. Важно то, что литерал функции - это выражение, которое дает функцию, которая еще не была определена где-либо еще, например, в C, &printf
не считается
РЕДАКТИРОВАТЬ, чтобы добавить: если у вас есть подлинная функция буквальное выражение <exp>
, вы должны быть в состоянии передать его функции f(<exp>)
или сразу применить его к аргументу, т.е. <exp>(5)
,
Мне интересно, какие языки позволяют писать функциональные литералы, которые являются рекурсивными. В статье " анонимной рекурсии" Википедии нет примеров программирования.
Давайте использовать рекурсивную факториальную функцию в качестве примера.
Вот те, которые я знаю:
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
и есть эквиваленты в Лиспе и Схеме. Обратите внимание, что привязка
fac
является локальным для выражения, поэтому все выражение фактически является анонимной функцией.
Есть ли другие?
16 ответов
Большинство языков поддерживают это с помощью Y комбинатора. Вот пример на Python (из поваренной книги):
# 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# будет выглядеть примерно так:
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:
my $factorial = do {
my $fac;
$fac = sub {
my $n = shift;
if ($n < 2) { 1 } else { $n * $fac->($n-1) }
};
};
print $factorial->(4);
do
блок не является строго необходимым; Я включил его, чтобы подчеркнуть, что результатом является истинная анонимная функция.
Ну кроме Common Lisp (labels
) и схема (letrec
), который вы уже упоминали, JavaScript также позволяет назвать анонимную функцию:
var foo = {"bar": function baz() {return baz() + 1;}};
что может быть удобнее, чем использованиеcallee
, (Это отличается от function
на высшем уровне; последний вызовет появление имени в глобальной области видимости, тогда как в первом случае имя появляется только в области действия самой функции.)
В Perl 6:
my $f = -> $n { if ($n <= 1) {1} else {$n * &?BLOCK($n - 1)} }
$f(42); # ==> 1405006117752879898543142606244511569936384000000000
Здесь вы перепутали терминологию, функциональные литералы не должны быть анонимными.
В javascript разница зависит от того, записана ли функция в виде оператора или выражения. Есть некоторая дискуссия о различии в ответах на этот вопрос.
Допустим, вы передаете свой пример функции:
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 уже упоминался. В большинстве языков это не обязательно, так как доступны лучшие методы. Вот ссылка на реализацию JavaScript.
В C# вам нужно объявить переменную для хранения делегата и присвоить ему значение null, чтобы убедиться, что он определенно назначен, а затем вы можете вызвать его из лямбда-выражения, которое вы ему присвоите:
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-комбинатор - более чистый подход. Есть еще одна оговорка с кодом, который я разместил выше: если вы измените значение fac
все, что пытается использовать старое значение, начнет терпеть неудачу, потому что лямбда-выражение захватило fac
сама переменная. (Что нужно, чтобы вообще нормально работать, конечно...)
Также кажется, что Mathematica позволяет вам определять рекурсивные функции, используя #0
обозначить саму функцию, как:
(expression[#0]) &
например, факториал:
fac = Piecewise[{{1, #1 == 0}, {#1 * #0[#1 - 1], True}}] &;
Это соответствует обозначениям #i
для ссылки на i-й параметр и соглашение о сценариях оболочки, согласно которому сценарий является его собственным 0-м параметром.
Вы можете сделать это в Matlab, используя анонимную функцию, которая использует самоанализ dbstack(), чтобы получить сам литерал функции и затем оценить ее. (Я признаю, что это обман, потому что dbstack, вероятно, следует считать экстралингвистическим, но он доступен во всех Matlabs.)
f = @(x) ~x || feval(str2func(getfield(dbstack, 'name')), x-1)
Это анонимная функция, которая отсчитывает от x и возвращает 1. Она не очень полезна, потому что в Matlab отсутствует оператор?: И запрещены блоки if внутри анонимных функций, поэтому трудно создать форму базового случая / рекурсивного шага.
Вы можете продемонстрировать, что это рекурсивно, вызвав 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
"Таким образом, вы неправильно поняли анонимные функции, речь идет не только о создании во время выполнения, но и о сфере действия. Рассмотрим этот макрос схемы:
(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;
Буквальное число. И даже если я воспользуюсь им сразу после этого, он исчезнет. В этом суть буквальных функций, а не создание во время выполнения как таковое.
Поскольку мне было любопытно, я действительно пытался придумать способ сделать это в MATLAB. Это можно сделать, но выглядит немного по-рубе-голдбергски:
>> 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 ((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
Анонимные функции существуют в C++0x с лямбдой, и они могут быть рекурсивными, хотя я не уверен насчет анонимности.
auto kek = [](){kek();}
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.
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))))))