Да си дефинираме рекурсия
Това е обновен вариант на публикация, която написах за elixir-lang.bg. Също, можете да я намерите на English на този блог.
Ще направим едно доста интересно упражнение. Ще използваме под-множество на езика, което не поддържа рекурсия и ще се опитаме да дефинираме рекурсивна функция. Под език, всъщност разбираме няколко езика. Добавих интересна функционалност към fron-end-а на моя блог engine - BlogitWeb, която ми позволява да пиша примери на повече от един програмен език и по този начин да дам възможност на читателите да изберат на кой от тях искат да ги четат.
В тази публикация ще използвамеelixir, erlang, ruby и javascript. Нека започнем.
Подготовка
За всеки от гореспоменатите езици за програмиране ще използваме само интерпретаторът му и няма да ползваме именовани функции. Ще забравим, че elixir, erlang, ruby и javascript поддържат именовани функции.
И така, нека пуснем избрания интерпретатор:
iex
erl
irb
node
# или някой модерен браузър (Firefox?)
Нека се ограничим с използването само на цели числа, прости аритметични операции с тях и сравнения между тях:
23
5 + 4
# 9
5 * 4
# 20
5 > 4
# true
5 < 4
# false
5 == 4
# false
23.
5 + 4.
% 9
5 * 4.
% 20
5 > 4.
% true
5 < 4.
% false
5 == 4.
% false
23
5 + 4
# 9
5 * 4
# 20
5 > 4
# true
5 < 4
# false
5 == 4
# false
23
5 + 4
// 9
5 * 4
// 20
5 > 4
// true
5 < 4
// false
5 === 4
// false
Ще се опитаме да дефинираме функцията Факториел. Точно поради тази причина ще използваме цели числа и операции с тях. Нека да видим, как можем да си дефинираме проста анонимна функция. Ще дефинираме функция, която събира своитр два аргумента и връща резултата:
fn (a, b) -> a + b end
fun (A, B) -> A + B end.
proc { |a, b| a + b }
# или
lambda { |a, b| a + b }
# или
-> (a, b) { a + b }
# Ще използваме синтаксиса '->' за тази публикация.
(function (a, b) { return a + b; });
// или
(a, b) => { return a + b; };
// Ще използваме ситаксиса '=>' за тази публикация.
Сега, можем да извикваме тази функция по следния начин:
(fn (a, b) -> a + b end).(4, 5)
# 9
(fun (A, B) -> A + B end)(4, 5).
% 9
-> (a, b) { a + b }.call(4, 5)
# 9
# или
-> (a, b) { a + b }[4, 5]
# 9
# Ще използваме синтаксиса '[]' за тази публикация.
(function (a, b) { return a + b; })(4, 5);
// 9
// или
((a, b) => { return a + b; })(4, 5);
// 9
Възможно е да се изпълнява различна логика, в зависимост от аргументите, които подаваме на функция:
(fn
0 -> 0
x -> x - 1
end).(3)
# 2
fun
(0) -> 0;
(X) -> X - 1
end(3).
% 2
-> (x) do
if x == 0
0
else
x - 1
end
end[3]
# 2
# or
-> (x) { x == 0 ? 0 : x - 1 }[3]
# 2
(
(x) => {
if (x === 0) {
return 0;
} else {
return x - 1;
}
}
)(3);
// 2
Това всъщност е функция, която връща числото преди това, което сме ѝ подали. За нула просто връща нула, тъй като нулата няма предходно число. Приемаме, че не знаем нищо за отрицателните числа в нашето малко подмножество на езика.
Добре! Вече знам всичко необходимо да си дефинираме функцията Факториел! Но така ли е наистина?
Факториел - Началото
Дотук знаем как да си дефинираме функция. Нека помислим какво трябва да бъде поведението на факториел функцията, ако я имахме дефинирана:
factorial.(0)
# 1
factorial.(1)
# 1 * factorial.(0) => 1
factorial.(2)
# 2 * factorial.(1) => 2
..........
factorial.(n)
# n * factorial.(n - 1) => n * n - 1 * n - 2 * ... * 1
Factorial(0).
% 1
Factorial(1).
% 1 * Factorial(0) => 1
Factorial(2).
% 2 * Factorial(1) => 2
..........
Factorial(N).
% N * Factorial(N - 1) => N * N - 1 * N - 2 * ... * 1
factorial[0]
# 1
factorial[1]
# 1 * factorial[0] => 1
factorial[2]
# 2 * factorial[1] => 2
..........
factorial[n]
# n * factorial[n - 1] => n * n - 1 * n - 2 * ... * 1
factorial(0);
// 1
factorial(1);
// 1 * factorial(0); => 1
// factorial(2);
// 2 * factorial(1); => 2
..........
factorial(n);
// n * factorial(n - 1); => n * n - 1 * n - 2 * ... * 1
Чудесно, нека сега да го дефинираме като анонимна функция! Подобно на функциите, които дефинирахме в предишната секция, би трябвало да можем да си дефинираме факториел и да я извикаме. Нека я дефинираме:
fn
0 -> 1
n -> n * me.(n - 1)
end
** (CompileError) iex:4: undefined function me/0
fun
(0) -> 1;
(N) -> N * Me(N - 1)
end.
* 3: variable 'Me' is unbound
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end
#<Proc:0x007ffe1cf6ade8@(irb):1 (lambda)>
# but
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end[2]
NameError: undefined local variable or method `me' for main:Object
(n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
};
// function ()
// but
(
(n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
}
)(2);
ReferenceError: me is not defined
Нормално. Дефинираме факториел без да му даваме име и поради това не знаем как да го извикаме рекурсивно от собственото му тяло.
Поради това, за да сложим нещо, слагаме me
Me
me
me
multi-code, което не съществува като име и имаме грешка.
От друга страна можем да пробваме да направим така, че да имаме такова име, което да е известно при дефиниране. Как? Ами като се подаде на функция от по-висок ред като аргумент:
fn (me) ->
fn
0 -> 1
n -> n * me.(n - 1)
end
end
#Function<6.52032458/1 in :erl_eval.expr/5>
fun (Me) ->
fun
(0) -> 1;
(N) -> N * Me(N - 1)
end
end.
% #Fun<erl_eval.6.99386804>
-> (me) do
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end
end
# => #<Proc:0x007f0c01778f08@(irb):17 (lambda)>
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
};
};
// function ()
Това работи и няма грешки. Въпросът е, какво да подадем за me
Me
me
me
multi-code?
Нека пробваме да извикаме тази дефиниция на факториел със самата нея.
За да опростим кода, позволяваме (и ще покажем синтаксиса) използването на променливи и присвояване на стойност на променлива:
factorial = fn (me) ->
fn
0 -> 1
n -> n * me.(n - 1)
end
end
#Function<6.52032458/1 in :erl_eval.expr/5>
Factorial = fun (Me) ->
fun
(0) -> 1;
(N) -> N * Me(N - 1)
end
end.
% #Fun<erl_eval.6.99386804>
factorial = -> (me) do
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end
end
# => #<Proc:0x007f0c01778f08@(irb):17 (lambda)>
let factorial = (me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
};
};
// function ()
Сега неко го извикаме с него като аргумент:
factorial.(factorial)
#Function<6.52032458/1 in :erl_eval.expr/5>
Factorial(Factorial).
% #Fun<erl_eval.6.99386804>
factorial[factorial]
# => #<Proc:0x007f0c01576f48@(irb):26 (lambda)>>
factorial(factorial);
// function factorial/<()
Работи. Върна функция. Може би тази функция е имплементацията на факториел? Така изглежда, да пробваме:
factorial.(factorial).(0)
# 1
factorial.(factorial).(1)
** (ArithmeticError) bad argument in arithmetic expression
(Factorial(Factorial))(0).
% 1
(Factorial(Factorial))(1).
** exception error: an error occurred when evaluating an arithmetic expression
in operator */2
called as 1 * #Fun<erl_eval.6.99386804>
factorial[factorial][0]
# => 1
factorial[factorial][1]
TypeError: Proc can't be coerced into Fixnum
factorial(factorial)(0);
// 1
factorial(factorial)(1);
// NaN
Добре. За 0
работи, а за 1
- не. Защо? Най-добре ще си го обясним ако разпишем какво
се случва, когато се изпълня функцията factorial
Factorial
factorial
factorial
multi-code:
(fn (me) ->
fn
0 -> 1
n -> n * me.(n - 1)
end
end).(fn (me) ->
fn
0 -> 1
n -> n * me.(n - 1)
end
end)
=>
factorial_factorial = fn
0 -> 1
n -> n * (fn (me) ->
fn
0 -> 1
n -> n * me.(n - 1)
end
end).(n - 1)
end
=>
factorial_factorial.(0) # Просто -> случаят 0 -> 1 се изпълнява и резултатът е 1:
1
factorial_factorial.(1) # Това е случаят n -> n * me.(n - 1)
=>
1 * (fn (me) ->
fn
0 -> 1
n -> n * me.(n - 1)
end
end).(0)
=>
1 * (fn
0 -> 1
n -> n * 0.(n - 1)
end)
# Този израз не може да се редуцира повече. Получаваме число, което се умножава
# по анонимна функция - ArithmeticError
(fun (Me) ->
fun
(0) -> 1;
(N) -> N * Me(N - 1)
end
end)(fun (Me) ->
fun
(0) -> 1;
(N) -> N * Me(N - 1)
end
end).
=>
FactorialFactorial = fun
(0) -> 1;
(N) -> N * (fun (Me) ->
fun
(0) -> 1;
(N) -> N * Me(N - 1)
end
end)(N - 1)
end.
=>
FactorialFactorial(0). % Просто -> случаят (0) -> 1 се изпълнява и резултатът е 1:
1
FactorialFactorial(1). % Това е случаят (N) -> N * Me(N - 1)
=>
1 * (fun (Me) ->
fun
(0) -> 1;
(N) -> N * Me(N - 1)
end
end)(0).
=>
1 * (fun
(0) -> 1;
(N) -> N * 0(N - 1)
end).
% Този израз не може да се редуцира повече. Получаваме число, което се умножава
% по анонимна функция - ArithmeticError
-> (me) do
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end
end[
-> (me) do
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end
end
]
=>
factorial_factorial = -> (n) do
if n == 0
1
else
n * -> (me) do
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end
end[n - 1]
end
end
=>
factorial_factorial[0] # Просто -> the n == 0 се изпълнява и резултатът е 1:
1
factorial_factorial[1] # Това е случаят n * me[n - 1]
=>
1 * ->(me) do
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end
end[0]
=>
1 * -> (n) do
if n == 0
1
else
n * 0[n - 1]
end
end
# Този израз не може да се редуцира повече. Получаваме число, което се умножава
# по анонимна функция :
# ruby се опитва да cast-не тази функция към число - TypeError
(
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
};
}
)(
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
};
}
);
=>
let factorialFactorial = (n) => {
if (n === 0) {
return 1;
} else {
return n * (
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
}
}
)(n - 1);
}
};
=>
factorialFactorial(0); // Просто -> the n === 0 се изпълнява и резултатът е 1:
1
factorialFactorial(1); // Това е случаят n * me(n - 1);
=>
1 * (
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
};
}
)(0);
=>
1 * (
(n) => {
if (n === 0) {
return 1;
} else {
return n * 0(n - 1);
}
}
);
// Този израз не може да се редуцира повече. Получаваме число, което се умножава
//по анонимна функция :
// javascript преобразува функцията в число и това е NaN. '1 * NaN' е също NaN.
Добре. Разбрахме защо за 0
работи, а за 1
- не. Сега като знаем какъв е проблемът,
нека помислим как да го решим.
Факториел II - Завинаги
Как да променим factorial
Factorial
factorial
factorial
multi-code за да работи в случая с 1
?
Нека изолираме само този случай. С 0
работи. Нека проработи с 1
.
Другите безкрайни на брой случаи ще ги оставим за после.
Да се върнем към редукцията която нарекохме factorial_factorial
FactorialFactorial
factorial_factorial
factorialFactorial
multi-code:
factorial_factorial = fn
0 -> 1
n -> n * (fn (me) ->
fn
0 -> 1
n -> n * me.(n - 1)
end
end).(n - 1)
end
# И когато подадем 1, стигаме до тук:
1 * (fn (me) ->
fn
0 -> 1
n -> n * me.(n - 1)
end
end).(0)
FactorialFactorial = fun
(0) -> 1;
(N) -> N * (fun (Me) ->
fun
(0) -> 1;
(N) -> N * Me(N - 1)
end
end)(N - 1)
end.
% И когато подадем 1, стигаме до тук:
1 * (fun (Me) ->
fun
(0) -> 1;
(N) -> N * Me(N - 1)
end
end)(0).
factorial_factorial = -> (n) do
if n == 0
1
else
n * -> (me) do
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end
end[n - 1]
end
end
# И когато подадем 1, стигаме до тук:
1 * ->(me) do
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end
end[0]
let factorialFactorial = (n) => {
if (n === 0) {
return 1;
} else {
return n * (
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
}
}
)(n - 1);
}
};
// И когато подадем 1, стигаме до тук:
1 * (
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
}
}
)(0);
Това не е ли 1 * factorial.(0)
1 * Factorial(0).
1 * factorial[0]
1 * factorial(0);
multi-code?
А знаем, че factorial.(0)
Factorial(0).
factorial[0]
factorial(0);
multi-code връща функция.
От друга страна factorial.(factorial).(0)
(Factorial(Factorial))(0).
factorial[factorial][0]
factorial(factorial)(0);
multi-code е случаят който работи за 0
.
Добре тогава - нека някак направим така че вместо 1 * factorial.(0)
Factorial(0).
1 * factorial[0]
1 * factorial(0);
multi-code да имаме 1 * factorial.(factorial).(0)
1 * (Factorial(Factorial))(0).
1 * factorial[factorial][0]
1 * factorial(factorial)(0);
multi-code.
Нека отново да погледнем дефиницията:
factorial = fn (me) ->
fn
0 -> 1
n -> n * me.(n - 1)
end
end
Factorial = fun (Me) ->
fun
(0) -> 1;
(N) -> N * Me(N - 1)
end
end.
factorial = ->(me) do
-> (n) do
if n == 0
1
else
n * me[n - 1]
end
end
end
let factorial = (me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(n - 1);
}
};
};
Когато подадем factorial
Factorial
factorial
factorial
multi-code на factorial
Factorial
factorial
factorial
multi-code в случая с 1
виждаме, че проблемът е,
че me.(0)
Me(0).
me[0]
me(0);
multi-code (me
Me
me
me
multi-code при това извикване се заменя с factorial
Factorial
factorial
factorial
multi-code) връща фунцкия,
а не факториел от 0
и си казахме че, factorial.(factorial).(0)
(Factorial(Factorial))(0).
factorial[factorial][0]
factorial(factorial)(0);
multi-code, което
връща факториел от 0
ще помогне.
Това означава, че ако вместо me.(n - 1)
Me(N - 1)
me[n - 1]
me(n - 1);
multi-code имаме me.(me).(n - 1)
(Me(Me))(N - 1)
me[me][n - 1]
me(me)(n - 1);
multi-code, при извикване
на factorial.(factorial).(1)
(Factorial(Factorial))(1).
factorial[factorial][1]
factorial(factorial)(1);
multi-code ще получим 1 * factorial.(factorial).(0)
1 * (Factorial(Factorial))(0).
1 * factorial[factorial][0]
1 * factorial(factorial)(0);
multi-code, което
е 1*0!
или 1!
. Нека да пробваме:
factorial = fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end
factorial.(factorial).(0) # 0!
# 1
factorial.(factorial).(1) # 1!
# 1
Factorial = fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end.
(Factorial(Factorial))(0). % 0!
% 1
(Factorial(Factorial))(1). % 1!
% 1
factorial = ->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end
factorial[factorial][0] # 0!
# 1
factorial[factorial][1] # 1!
# 1
let factorial = (me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
};
factorial(factorial)(0); // 0!
// 1
factorial(factorial)(1); // 1!
// 1
Успяхме! Имаме 0!
и 1!
. Сега е време да помислим как да имплементираме n!
.
Нека да пробваме с 2!
, да видим грешката и да разгърнем извикването, както направихме
с factorial.(factorial).(1)
(Factorial(Factorial))(1).
factorial[factorial][1]
factorial(factorial)(1);
multi-code преди малко:
factorial.(factorial).(2)
# 2
# Интересно! Работи и връща верен резултат : 2 * 1 * 0! - 2!
factorial.(factorial).(3)
# 6 or 3!
factorial.(factorial).(5)
# 120 or 5!
factorial.(factorial).(10)
# 3628800 or 10!
(Factorial(Factorial))(2).
% 2
% Интересно! Работи и връща верен резултат : 2 * 1 * 0! - 2!
(Factorial(Factorial))(3).
% 6 or 3!
(Factorial(Factorial))(5).
% 120 or 5!
(Factorial(Factorial))(10).
% 3628800 or 10!
factorial[factorial][2]
# 2
# Интересно! Работи и връща верен резултат : 2 * 1 * 0! - 2!
factorial[factorial][3]
# 6 or 3!
factorial[factorial][5]
# 120 or 5!
factorial[factorial][10]
# 3628800 or 10!
factorial(factorial)(2);
// 2
// Интересно! Работи и връща верен резултат : 2 * 1 * 0! - 2!
factorial(factorial)(3);
// 6 or 3!
factorial(factorial)(5);
// 120 or 5!
factorial(factorial)(10);
// 3628800 or 10!
Това е работещ факториел написан с анонимни функции. Това е рекурсия, написана само с анонимни функции. Даже можем да го напишем без да ползваме променливи така:
(fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(5)
# 120
((fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end)(fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end))(5).
% 120
->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end[
->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end
][5]
# 120
# Ето и по-кратка версия:
->(me) { -> (n) { n == 0 ? 1 : n * me[me][n - 1] } } [
->(me) { -> (n) { n == 0 ? 1 : n * me[me][n - 1] } }
][5]
# 120
(
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(5);
// 120
Имплементирахме своя рекурсия. Но как работи?
Факториел III - Безкрайност
Нека да видим какво се случва с factorial.(factorial).(3)
(Factorial(Factorial))(3).
factorial[factorial][3]
factorial(factorial)(3);
multi-code.
(fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(3)
=>
(fn
0 -> 1
n -> n * (fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(n - 1)
end).(3)
=>
3 * (fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(2)
=>
3 * (fn
0 -> 1
n -> n * (fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(n - 1)
end).(2)
# Забелязваме, че това е същата функция от по-горе, на която подадохме 3.
# Ако я наречем 'f' (factorial.(factorial)), този израз е всъщност 3 * f.(2) (3 * factorial.(factorial).(2))
=>
3 * 2 * (fn
0 -> 1
n -> n * (fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(n - 1)
end).(1)
# 3 * 2 * f.(1)
=>
3 * 2 * 1 * (fn
0 -> 1
n -> n * (fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end).(n - 1)
end).(0)
# 3 * 2 * 1 * f.(0), но тук 0 е случая 0 -> 1 и получаваме:
=>
3 * 2 * 1 * 1 = 6 # 3!
((fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end)(fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end))(3).
=>
(fun
(0) -> 1;
(N) -> N * ((fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end)(fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end))(N - 1)
end)(3).
=>
3 * ((fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end)(fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end))(2).
=>
3 * (fun
(0) -> 1;
(N) -> N * ((fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end)(fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end))(N - 1)
end)(2).
% Забелязваме, че това е същата функция от по-горе, на която подадохме 3.
% Ако я наречем 'F' (Factorial(Factorial)), този израз е всъщност 3 * F(2). (3 * (Factorial(Factorial))(2).)
=>
3 * 2 * (fun
(0) -> 1;
(N) -> N * ((fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end)(fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end))(N - 1)
end)(1).
% 3 * 2 * F(1).
=>
3 * 2 * 1 * (fun
(0) -> 1;
(N) -> N * ((fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end)(fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end))(N - 1)
end)(0).
% 3 * 2 * 1 * F(0).
% Но тук 0 е случая (0) -> 1 и получаваме:
=>
3 * 2 * 1 * 1 = 6. % 3!
->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end[
->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end
][3]
=>
-> (n) do
if n == 0
1
else
n * ->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end[
->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end
][n - 1]
end
end[3]
=>
3 * ->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end[
->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end
][2]
=>
3 * -> (n) do
if n == 0
1
else
n * ->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end[
->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end
][n - 1]
end
end[2]
# Забелязваме, че това е същата функция от по-горе, на която подадохме 3.
# Ако я наречем 'f' (factorial[factorial]), този израз е всъщност 3 * f[2] (3 * factorial[factorial][2]).
=>
3 * 2 * -> (n) do
if n == 0
1
else
n * ->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end[
->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end
][n - 1]
end
end[1]
# 3 * 2 * f[1]
=>
3 * 2 * 1 * -> (n) do
if n == 0
1
else
n * ->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end[
->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end
][n - 1]
end
end[0]
# 3 * 2 * 1 * f[0]
# Но в този случай n е 0, от което следва, че f[0] връща 1:
=>
3 * 2 * 1 * 1 == 6 # 3!
(
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(3);
=>
(
(n) => {
if (n === 0) {
return 1;
} else {
return n * (
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(n - 1);
}
}
)(3);
// или
factorial(factorial)(3);
=>
(
(n) => {
if (n === 0) {
return 1;
} else {
return n * factorial(factorial)(n - 1);
}
}
)(3);
// ако редуцираме с още една стъпка =>
3 * (
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(2);
// Забелязваме, че това е същата функция от по-горе, на която подадохме 3.
// Ако я наречем 'f' (factorial(factorial)), този израз е всъщност 3 * f(2), или:
// or
3 * factorial(factorial)(2);
// Но същите пребразувания могат да се приложат и към factorial(factorial)(2) =>
3 * 2 * (
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(1);
// или
3 * 2 * factorial(factorial)(1);
=>
3 * 2 * 1 * (
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(
(me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
}
)(0);
// или
3 * 2 * 1 * factorial(factorial)(0);
// Но тук n е 0, от което следва, че factorial(factorial)(0) връща 1:
=>
3 * 2 * 1 * 1 === 6; // 3!
В общи линии достигнахме до повтаряща се функция, която се държи като факториел. В този ред на мисли:
factorial = fn (me) ->
fn
0 -> 1
n -> n * me.(me).(n - 1)
end
end
Factorial = fun (Me) ->
fun
(0) -> 1;
(N) -> N * (Me(Me))(N - 1)
end
end.
factorial = ->(me) do
-> (n) do
if n == 0
1
else
n * me[me][n - 1]
end
end
end
let factorial = (me) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * me(me)(n - 1);
}
};
};
Това е грешно име за тази функция.
Ето я истинската дефиниция на factorial
Factorial
factorial
factorial
multi-code като анонимна функция:
factorial = (fn (f) ->
fn
0 -> 1
n -> n * f.(f).(n - 1)
end
end).(fn (f) ->
fn
0 -> 1
n -> n * f.(f).(n - 1)
end
end)
factorial.(5)
# 120
Factorial = (fun (F) ->
fun
(0) -> 1;
(N) -> N * (F(F))(N - 1)
end
end)(fun (F) ->
fun
(0) -> 1;
(N) -> N * (F(F))(N - 1)
end
end).
Factorial(5).
% 120
factorial = ->(f) do
-> (n) do
if n == 0
1
else
n * f[f][n - 1]
end
end
end[
->(f) do
-> (n) do
if n == 0
1
else
n * f[f][n - 1]
end
end
end
]
factorial[5]
# 120
let factorial = (
(f) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * f(f)(n - 1);
}
};
}
)(
(f) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * f(f)(n - 1);
}
};
}
);
factorial(5);
// 120
И тук можем да спрем.
Но защо да не генерализираме малко тази конструкция, така
че да работи и за други функции на един аргумент - да речем Фибуначи.
Използвайки тази идея можем да дефинираме функцията намираща n
-тото число от редицата на Фибуначи така:
fib = (fn (f) ->
fn
0 -> 1
1 -> 2
n -> f.(f).(n - 1) + f.(f).(n - 2)
end
end).(fn (f) ->
fn
0 -> 1
1 -> 2
n -> f.(f).(n - 1) + f.(f).(n - 2)
end
end)
fib.(3)
# 5
Fib = (fun (F) ->
fun
(0) -> 1;
(1) -> 2;
(N) -> (F(F))(N - 1) + (F(F))(N - 2)
end
end)(fun (F) ->
fun
(0) -> 1;
(1) -> 2;
(N) -> (F(F))(N - 1) + (F(F))(N - 2)
end
end).
Fib(3).
% 5
fib = ->(f) do
-> (n) do
if n == 0 then 1
elsif n == 1 then 2
else f[f][n - 1] + f[f][n - 2]
end
end
end[
->(f) do
-> (n) do
if n == 0 then 1
elsif n == 1 then 2
else f[f][n - 1] + f[f][n - 2]
end
end
end
]
fib[3]
# 5
let fib = (
(f) => {
return (n) => {
if (n === 0) {
return 1;
} else if (n === 1) {
return 2;
} else {
return f(f)(n - 1) + f(f)(n - 2);
}
};
}
)(
(f) => {
return (n) => {
if (n === 0) {
return 1;
} else if (n === 1) {
return 2;
} else {
return f(f)(n - 1) + f(f)(n - 2);
}
};
}
);
fib(3);
// 5
Факториел IV - Генерализации
Абстракцията е мого важна част от програмирането.
- Добре е да не повтаряме кода си, ако можем.
- Още по-добре е да преизползваме толкова код, колкото можем.
Ще създадем функция, чрез която ще можем да си дефинираме рекурсивни анонимни функции на един аргумент. За да направим това, ще ни трябват следните две дефиниции:
- Ще наричаме функции които имат само свързани променливи комбинатори. Това са функции които зависят само от променливи декларирани като техни аргументите или в телата им:
id = fn (x) -> x end # Това е комбинатор - I комбинаторът или identity комбинаторът.
y = 5
fn (x) -> x + y end
# Това не е комбинатор.
# Зависи от променлива, която не е декларирана в тялото на функцията или не е сред аргументите ѝ.
I = fun (X) -> X end. % Това е комбинатор - I комбинаторът или identity комбинаторът.
Y = 5.
fun (X) -> X + Y end.
% Това не е комбинатор.
% Зависи от променлива, която не е декларирана в тялото на функцията или не е сред аргументите ѝ.
I = -> (x) { x } # Това е комбинатор - I комбинаторът или identity комбинаторът.
-> (x) { x + y }
# Това не е комбинатор.
# Зависи от променлива, която не е декларирана в тялото на функцията или не е сред аргументите ѝ.
let I = (x) => x; // Това е комбинатор - I комбинаторът или identity комбинаторът.
(x) => x + y;
// Това не е комбинатор.
// Зависи от променлива, която не е декларирана в тялото на функцията или не е сред аргументите ѝ.
- Ще дефинираме един комбинатор, наречен Омега комбинатор или looping комбинатор:
o = fn (f) -> f.(f) end
o.(id) == id
# true
o.(id) == id.(id)
# true
O = fun (F) -> F(F) end.
O(I) == I.
% true
O(I) == I(I).
% true
O = -> (f) { f[f] }
O[I] == I
# true
O[I] == I[I]
# true
let O = (f) => f(f);
O(I) === I;
// true
O(I) === I(I);
// true
Да се върнем към нашата факториел функция:
factorial = (fn (f) ->
fn
0 -> 1
n -> n * f.(f).(n - 1)
end
end).(fn (f) ->
fn
0 -> 1
n -> n * f.(f).(n - 1)
end
end)
=>
factorial = fn (g) ->
g.(g)
end.(fn (f) ->
fn
0 -> 1
n -> n * f.(f).(n - 1)
end
end)
Factorial = (fun (F) ->
fun
(0) -> 1;
(N) -> N * (F(F))(N - 1)
end
end)(fun (F) ->
fun
(0) -> 1;
(N) -> N * (F(F))(N - 1)
end
end).
=>
Factorial = (fun (G) ->
G(G)
end)(fun (F) ->
fun
(0) -> 1;
(N) -> N * (F(F))(N - 1)
end
end).
factorial = ->(f) do
-> (n) do
if n == 0
1
else
n * f[f][n - 1]
end
end
end[
->(f) do
-> (n) do
if n == 0
1
else
n * f[f][n - 1]
end
end
end
]
=>
factorial = ->(g) do
g[g]
end[
->(f) do
-> (n) do
if n == 0
1
else
n * f[f][n - 1]
end
end
end
]
let factorial = (
(f) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * f(f)(n - 1);
}
};
}
)(
(f) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * f(f)(n - 1);
}
};
}
);
=>
let factorial = ((g) => g(g))(
(f) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * f(f)(n - 1);
}
};
}
);
Да, факториелът е представен като извикване на тази прото-факториел функция със себе си. С други думи:
factorial = o.(fn (f) ->
fn
0 -> 1
n -> n * f.(f).(n - 1)
end
end)
Factorial = O(fun (F) ->
fun
(0) -> 1;
(N) -> N * (F(F))(N - 1)
end
end).
factorial = o[
->(f) do
-> (n) do
if n == 0
1
else
n * f[f][n - 1]
end
end
end
]
# или
factorial = o[->(f) { -> (n) { n == 0 ? 1 : n * f[f][n - 1] } }]
let factorial = O(
(f) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * f(f)(n - 1);
}
};
}
);
Нека да опитаме да извадим логиката специфична за факториела във функция, която прилича на класическата дефиниция на факториел. Можем ли?
factorial = o.(fn (f) ->
proto_factorial = fn g ->
fn
0 -> 1
n -> n * g.(n - 1)
end
end
proto_factorial.(fn (x) -> f.(f).(x) end)
end)
Factorial = O(fun (F) ->
ProtoFactorial = fun (G) ->
fun
(0) -> 1;
(N) -> N * (G)(N - 1)
end
end,
ProtoFactorial(fun (X) -> (F(F))(X) end)
end).
factorial = o[
->(f) do
proto_factorial = -> (g) do
-> (n) do
if n == 0
1
else
n * g[n - 1]
end
end
end
proto_factorial[-> (x) { f[f][x] }]
end
]
let factorial = O(
(f) => {
let protoFactorial = (g) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * g(n - 1);
}
};
};
return protoFactorial((x) => f(f)(x));
}
);
Сега изглежда по-сложно, но не е. Извадихме това, което прави факториела - факториел, есенцията на имплементацията му в обособена прото факториел функция. А защо направихме това? Защото е стъпка към целта - да изкараме есенцията на факториела от конструкцията, която позволява рекурсия с анонимни функции. Всъщност ние сме доста близко до тази цел. Следващата стъпка ще е да извадим прото факториел функцията навън:
proto_factorial = fn g ->
fn
0 -> 1
n -> n * g.(n - 1)
end
end
factorial = (fn (h) ->
o.(fn (f) ->
h.(fn (x) -> f.(f).(x) end)
end)
end).(proto_factorial)
ProtoFactorial = fun (G) ->
fun
(0) -> 1;
(N) -> N * (G)(N - 1)
end
end.
Factorial = (fun (H) ->
O(fun (F) ->
H(fun (X) -> (F(F))(X) end)
end)
end)(ProtoFactorial).
proto_factorial = -> (g) do
-> (n) do
if n == 0
1
else
n * g[n - 1]
end
end
end
factorial = -> (h) do
o[-> (f) { h[-> (x) { f[f][x] }] } ]
end[proto_factorial]
let protoFactorial = (g) => {
return (n) => {
if (n === 0) {
return 1;
} else {
return n * g(n - 1);
}
};
};
let factorial = (
(h) => {
return O(
(f) => h((x) => f(f)(x))
);
}
)(protoFactorial);
Изваждането на една функция е лесно, просто обвиваме тялото на функцията която я дефинира в друга функция, вадим дефиницията навън и я подаваме на обвиващата функция. Сега имаме конструкцията, която ни позволява да построим рекурсия за анонимни функции с един аргумент. Ето я и нея:
fn (h) ->
o.(fn (f) ->
h.(fn (x) -> f.(f).(x) end)
end)
end
=>
y = fn (h) ->
(fn (f) ->
f.(f)
end).(fn (g) ->
h.(fn (x) -> g.(g).(x) end)
end)
end
fun (H) ->
O(fun (F) ->
H(fun (X) -> (F(F))(X) end)
end)
end.
=>
Y = fun (H) ->
(fun (F) ->
F(F)
end)(fun (G) ->
H(fun (X) -> (G(G))(X) end)
end)
end.
-> (h) do
o[-> (f) { h[-> (x) { f[f][x] }] } ]
end
=>
Y = -> (h) do
-> (f) { f[f] }[
-> (g) { h[-> (x) { g[g][x] }] }
]
end
(h) => {
return O(
(f) => h((x) => f(f)(x))
);
};
=>
let Y = (h) => {
return ((f) => f(f))(
(g) => h((x) => g(g)(x))
);
};
Ето как с нея можем да си построим функцията за намиране на N-тото число на Фибоначи:
proto_fib = fn (f) ->
fn
0 -> 1
1 -> 2
n -> f.(n - 1) + f.(n - 2)
end
end
fib = y.(proto_fib)
ProtoFib = fun (F) ->
fun
(0) -> 1;
(1) -> 2;
(N) -> F(N - 1) + F(N - 2)
end
end.
Fib = Y(ProtoFib).
proto_fib = -> (f) do
-> (n) do
if n == 0 then 1
elsif n == 1 then 2
else f[n - 1] + f[n - 2]
end
end
end
fib = Y[proto_fib]
let protoFib = (f) => {
return (n) => {
if (n === 0) {
return 1;
} else if (n === 1) {
return 2;
} else {
return f(n - 1) + f(n - 2);
}
};
};
let fib = Y(protoFib);
И така изведохме небезизвестния Y комбинатор. Това е функция от по висок ред, която позволява функции без имена да поддържат рекурсия. Този изведен от нас го позволява само за едно-аргументни функции. Освен това е вид Y комбинатор, по-известен като Z комбинатор, защото elixir, erlang, ruby и javascript не са lazy езици и си изчисляват аргументите преди да ги подадат на функцията.
Друго известно наименование на Y комбинатора е the fixed point combinator. Тоест връща неподвижната точка
на функцията, която приема като аргумент. Неподвижна точка x
за функция f
е такава стойност, за която
f(x) = x
. Нека да проверим:
fib = y.(proto_fib)
proto_fib.(fib).(5)
# 13
fib.(5)
# 13
proto_fib.(proto_fib.(fib)).(5)
# 13
Fib = Y(ProtoFib).
(ProtoFib(Fib))(5).
% 13
Fib(5).
% 13
(ProtoFib(ProtoFib(Fib)))(5).
% 13
fib = Y[proto_fib]
proto_fib[fib][5]
# 13
fib[5]
# 13
proto_fib[proto_fib[fib]][5]
# 13
fib = Y(protoFib);
protoFib(fib)(5);
// 13
fib(5);
// 13
protoFib(protoFib(fib))(5);
// 13
Ако по горе proto_fib
ProtoFib
proto_fib
protoFib
multi-code означим с f
, a y.(proto_fib)
Y(ProtoFib)
y[proto_fib]
Y(protoFib)
multi-code с x
, то x
е неподвижна точка на f
.
Това е така, защото равенството е изпълнено f(x) = x = f(f(x)) = f(..(f(..(f(x))..))..)
.
Комбинаторът Y няма да ви трябва практически, ще работите с именовани функции, все пак, но е една много красива конструкция. Работи някак магически и все пак няма никаква магия в него. Разписването и разбирането му, никак не е безмислено упражнение. Видяхме как абстрахираме и генерализираме концепции чрез функционално програмиране, също се запознахме с няколко термина идващи от ламбда-смятането.
Това е всичко… засега.