Каждый знает, как написать функцию, которая вычисляет факториал числа, поскольку является одним из основных примеров использования рекурсии.
Erlang ничем не отличается от других языков программирования в своей поддержке рекурсии, но его возможности сопоставления с образцом обеспечивают ярлык для перечисления базового случая рекурсии вместе с общим:
recursive_factorial(0) -> 1; recursive_factorial(N) -> recursive_factorial(N - 1) * N.
Вот небольшой тест для этой функции:
simple_test() -> ?assertEqual(6, recursive_factorial(3)), ?assertEqual(2, recursive_factorial(2)), ?assertEqual(1, recursive_factorial(1)), ?assertEqual(1, recursive_factorial(1)).
Как и во всех определениях функций, предложения и их тела разделяются точкой с запятой; операторы, принадлежащие одной и той же функции, вместо этого разделяются запятыми. Последний оператор каждой функции возвращается.
Предложения сопоставляются в порядке их определения, поэтому базовый случай, когда аргумент равен 0, остановит рекурсию для этой функции; это ничем не отличается от if (), содержащего ранний возврат. Однако не только в долгосрочной перспективе вы устали бы писать такие условные выражения, но сопоставление с образцом также является более мощным и лаконичным:
print_name({person, Name, _Surname}) -> io:format("~s~n", Name).
В этом определении функции мы извлекаем второй элемент набора мощности 3, но только если его первым элементом является атом person . Второй элемент привязан к переменной Name, в то время как мы сигнализируем, что хотим игнорировать третий, добавив префикс имени Surname к _ (мы также можем просто написать _).
Когда сопоставления недостаточно, у нас также есть возможность добавления охранных предложений. Переопределение факториала таким образом:
guard_factorial(N) when N < 2 -> 1; guard_factorial(N) -> guard_factorial(N - 1) * N.
позволяет нам определять предложения программно, а не полагаться на структуру аргументов.
Мы можем сделать ту же работу внутри кода Erlang, если не хотим извлекать его в отдельную функцию:
case_factorial(N) -> case N of 0 -> 1; _ -> case_factorial(N - 1) * N end.
Конец является частью саза, в то время как точка обозначает конец функции. Обратите внимание, что _ является заполнителем Erlang для чего-либо и фактически всегда будет соответствовать любому значению
Анонимные функции
В каждом функциональном языке вы можете определять анонимные функции, которые могут храниться в переменных и передаваться другим функциям (которые называются более высокого порядка, напоминая математическую концепцию логики высшего порядка).
В Erlang легко определить такую функцию:
Function = fun(Argument) -> dosomething() end.
На этот раз конец является частью определения функции.
Эти функции могут быть вызваны напрямую во многих случаях:
Function(ActualArgument).
Или с apply / 2:
apply(Function, [ActualArgument]).
Второй аргумент apply / 2 — это всегда список. Существует также версия apply / 3, где первые два аргумента — это атомы, описывающие модуль и имя функции:
?assertEqual(6, apply(factorial_04, guard_factorial, [3])),
Как насчет рекурсии в анонимных функциях?
Это где это становится сложным. При реализации рекурсии с именованными функциями вы можете ссылаться на функцию по имени, чтобы вызывать ее внутри себя. Посмотрите еще раз на наш recursive_factorial / 1:
Но когда функция определена анонимно, она не может так легко ссылаться на себя. Чтобы повториться, функция должна быть передана сама себе в качестве аргумента:
Factorial = fun(N, F) -> case N of 0 -> 1; _ -> apply(F, [N-1, F]) * N end end, ?assertEqual(6, apply(Factorial, [3, Factorial])),
Оказывается, этот шаблон является общим и может быть обобщен для применения к любой функции со знаменитым Y комбинатором.
Комбинатор Y — это функция высшего порядка, которая принимает функцию F в качестве входных данных. Эта функция разработана таким образом, чтобы иметь два аргумента карри:
FactorialY = fun(F) -> fun (N) -> case N of 0 -> 1; _ -> F(N-1) * N end end end, ?assertEqual(6, apply(y(FactorialY), [3])).
То, что выходит из Y-комбинатора, просто забавно (N). Если вам интересно, вот комбинатор, определенный в Erlang:
y(M) -> G = fun (F) -> M(fun(A) -> (F(F))(A) end) end, G(G).
но так же, как и вы, я все еще пытаюсь обернуть голову вокруг этого. ? Это, вероятно, признак того, что объектно-ориентированное программирование более распространено, чем функциональное программирование: оно немного ближе к тому, что думают обычные люди, а функциональное программирование ближе к математическим и теоретическим аспектам информатики. И теория не самая простая вещь, чтобы понять …
Вывод
У нас не осталось места (и воздуха), поэтому мы поговорим о хвостовой рекурсии в следующей статье. Мы также немного расширим обсуждение анонимных функций и их полезности. Помните, что весь код, обсуждаемый в этой статье, доступен в репозитории Github этой серии .