Просто чтобы уточнить наименование, они обе функции. Одна - именованная функция, а другая - анонимная. Но вы правы, они работают несколько иначе, и я собираюсь проиллюстрировать, почему они работают так.
Начнем со второго fn
. fn
это закрытие, похожее на lambda
в Ruby. Мы можем создать его следующим образом:
x = 1
fun = fn y -> x + y end
fun.(2) #=> 3
Функция также может иметь несколько предложений:
x = 1
fun = fn
y when y < 0 -> x - y
y -> x + y
end
fun.(2) #=> 3
fun.(-2) #=> 3
Теперь давайте попробуем что-то другое. Давайте попробуем определить разные предложения, ожидающие разного количества аргументов:
fn
x, y -> x + y
x -> x
end
** (SyntaxError) cannot mix clauses with different arities in function definition
о нет! Мы получаем ошибку! Мы не можем смешивать предложения, которые ожидают другое количество аргументов. Функция всегда имеет фиксированную арность.
Теперь поговорим о названных функциях:
def hello(x, y) do
x + y
end
Как и ожидалось, у них есть имя, и они также могут получить некоторые аргументы. Однако они не являются замыканиями:
x = 1
def hello(y) do
x + y
end
Этот код не удастся скомпилировать, потому что каждый раз, когда вы видите a def
, вы получаете пустую переменную scope. Это важное различие между ними. Мне особенно нравится тот факт, что каждая именованная функция начинается с чистого листа, и вы не смешиваете переменные разных областей действия. У вас есть четкая граница.
Мы могли бы получить указанную выше функцию hello как анонимную функцию. Вы упомянули это сами:
other_function(&hello(&1))
И тогда вы спросили, почему я не могу просто передать это, hello
как на других языках? Это потому, что функции в Elixir идентифицируются по имени и арности. Таким образом, функция, которая ожидает два аргумента, отличается от функции, которая ожидает три аргумента, даже если они имеют одно и то же имя. Так что, если бы мы просто прошли hello
, мы бы не поняли, что hello
вы на самом деле имели в виду. Тот, с двумя, тремя или четырьмя аргументами? Это в точности та же самая причина, по которой мы не можем создать анонимную функцию с предложениями с разными арностями.
Начиная с Elixir v0.10.1, у нас есть синтаксис для захвата именованных функций:
&hello/1
Это захватит локальную именованную функцию hello с arity 1. Во всем языке и его документации очень часто идентифицируются функции в этом hello/1
синтаксисе.
Именно поэтому Elixir использует точку для вызова анонимных функций. Поскольку вы не можете просто передавать hello
как функцию, вместо этого вам нужно явно захватить ее, есть естественное различие между именованными и анонимными функциями, и отдельный синтаксис для вызова каждой делает все немного более явным (Лисперс был бы знаком с этим из-за обсуждения Lisp 1 и Lisp 2).
В целом, это причины, по которым у нас есть две функции и почему они ведут себя по-разному.
f()
(без точки).is_function/2
в гвардии.is_function(f, 2)
проверяет, имеет ли он 2.SomeFun()
иsome_fun()
. В Elixir, если мы удалим точку, они будут одинаковымиsome_fun()
иsome_fun()
потому что переменные используют тот же идентификатор, что и имена функций. Отсюда и точка.Я не знаю, насколько это будет полезно для кого-то еще, но способ, которым я наконец обернул голову вокруг концепции, заключался в том, чтобы понять, что функции эликсира не являются функциями.
Все в эликсире является выражением. Так
это не функция, а выражение, возвращаемое выполнением кода в
my_function
. На самом деле есть только один способ получить «функцию», которую вы можете передать в качестве аргумента, и использовать анонимную функцию.Заманчиво ссылаться на обозначение fn или & как на указатель на функцию, но на самом деле это гораздо больше. Это закрытие окружающей среды.
Если вы спросите себя:
Нужна ли мне среда исполнения или значение данных в этом месте?
А если вам нужно выполнить использование fn, то большинство трудностей станут намного понятнее.
источник
MyModule.my_function(foo)
это выражение, ноMyModule.my_function
«могло» быть выражением, которое возвращает функцию «объект». Но так как вам нужно рассказать об искусстве, вам нужно что-то вродеMyModule.my_function/1
этого. И я думаю, что они решили, что&MyModule.my_function(&1)
вместо этого лучше использовать синтаксис, который позволяет выражать арность (и служит другим целям).()
.()
&MyModule.my_function/1
вот как вы можете передать это как функцию.Я никогда не понимал, почему объяснения этого так сложны.
Это на самом деле просто небольшое различие в сочетании с реалиями «выполнения функций без паренов» в стиле Ruby.
Для сравнения:
Для того, чтобы:
Хотя оба они просто идентификаторы ...
fun1
представляет собой идентификатор , который описывает именованную функцию , определенную сdef
.fun2
является идентификатором, который описывает переменную (которая содержит ссылку на функцию).Подумайте, что это значит, когда вы видите
fun1
илиfun2
в каком-то другом выражении? Оценивая это выражение, вызываете ли вы ссылочную функцию или просто ссылаетесь на значение из памяти?Нет хорошего способа узнать это во время компиляции. Ruby может позволить себе роскошный анализ пространства имен переменных, чтобы выяснить, не является ли привязка переменной затенением функции в определенный момент времени. Эликсир, будучи скомпилированным, не может этого сделать. Это то, что делает точка-нотация, она говорит Elixir, что она должна содержать ссылку на функцию и что она должна быть вызвана.
И это действительно сложно. Представьте, что не было точечной нотации. Рассмотрим этот код:
Учитывая приведенный выше код, я думаю, что вполне понятно, почему вы должны дать Эликсиру подсказку. Представляете, должна ли каждая переменная де-ссылки проверять функцию? В качестве альтернативы, представьте, какие героические действия были бы необходимы, чтобы всегда делать вывод, что разыменование переменной использует функцию?
источник
Возможно, я ошибаюсь, поскольку никто не упомянул об этом, но у меня также сложилось впечатление, что причиной этого является также наследие ruby - возможность вызывать функции без скобок.
Arity явно вовлечен, но давайте отложим это на некоторое время и будем использовать функции без аргументов. В таком языке, как javascript, где скобки являются обязательными, легко сделать различие между передачей функции в качестве аргумента и вызовом функции. Вы называете это только тогда, когда используете скобки.
Как видите, называть это или нет не имеет большого значения. Но эликсир и рубин позволяют вызывать функции без скобок. Это выбор дизайна, который мне лично нравится, но у него есть побочный эффект: вы не можете использовать только имя без скобок, потому что это может означать, что вы хотите вызвать функцию. Вот для чего
&
. Если вы оставляете arity appart на секунду, добавление имени вашей функции&
означает, что вы явно хотите использовать эту функцию в качестве аргумента, а не то, что возвращает эта функция.Теперь анонимная функция немного отличается тем, что она в основном используется в качестве аргумента. Опять же, это выбор дизайна, но рациональным является то, что он в основном используется функциями итераторов, которые принимают функции в качестве аргументов. Очевидно, что вам не нужно использовать,
&
потому что они уже считаются аргументами по умолчанию. Это их цель.Теперь последняя проблема заключается в том, что иногда вам приходится вызывать их в своем коде, потому что они не всегда используются с функцией итератора, или вы можете кодировать итератор самостоятельно. Для небольшой истории, поскольку ruby является объектно-ориентированным, основным способом сделать это было использование
call
метода объекта. Таким образом, вы можете сохранить согласованность поведения необязательных скобок.Теперь кто-то придумал ярлык, который в основном выглядит как метод без имени.
Опять же, это выбор дизайна. Теперь эликсир не является объектно-ориентированным и, следовательно, call не использует первую форму наверняка. Я не могу говорить за Хосе, но похоже, что вторая форма использовалась в эликсире, потому что она все еще выглядит как вызов функции с дополнительным символом. Это достаточно близко к вызову функции.
Я не думал обо всех плюсах и минусах, но похоже, что на обоих языках вы можете избежать неприятностей только с помощью скобок, если сделать скобки обязательными для анонимных функций. Кажется, что это так:
Обязательные скобки VS Немного отличные обозначения
В обоих случаях вы делаете исключение, потому что вы заставляете оба вести себя по-разному. Поскольку есть разница, вы можете также сделать это очевидным и перейти к другой записи. Обязательные скобки выглядят естественно в большинстве случаев, но очень запутанно, когда дела идут не так, как планировалось.
Ну вот. Возможно, это не лучшее объяснение в мире, потому что я упростил большинство деталей. Кроме того, большинство из них - выбор дизайна, и я попытался обосновать их, не осуждая их. Я люблю эликсир, я люблю рубин, мне нравятся вызовы функций без скобок, но, как и вы, я нахожу последствия весьма ошибочными время от времени.
А в эликсире это просто лишняя точка, тогда как в рубине у вас есть блоки поверх этого. Блоки удивительны, и я удивлен, как много вы можете сделать только с блоками, но они работают только тогда, когда вам нужна только одна анонимная функция, которая является последним аргументом. Тогда, поскольку вы должны иметь возможность иметь дело с другими сценариями, здесь возникает путаница в методе / lambda / proc / block.
Во всяком случае ... это выходит за рамки.
источник
Об этом поведении есть отличный пост в блоге: ссылка
Два типа функций
Точка в звонке
fn
источник
Elixir имеет дополнительные скобки для функций, в том числе функции с 0 арностью. Давайте рассмотрим пример того, почему он делает отдельный синтаксис вызова важным:
Не делая различий между двумя типами функций, мы не можем сказать, что
Insanity.dive
означает: получить саму функцию, вызвать ее или также вызвать полученную анонимную функцию.источник
fn ->
Синтаксис для использования анонимных функций. Выполнение var. () Просто говорит эликсиру, что я хочу, чтобы вы взяли этот var с функцией в нем и запустили вместо того, чтобы ссылаться на var как на нечто, просто удерживающее эту функцию.У Elixir есть такая общая схема, где вместо логики внутри функции, чтобы увидеть, как что-то должно выполняться, мы сопоставляем различные функции в зависимости от того, какой у нас ввод. Я предполагаю, что именно поэтому мы относимся к вещам по определению в
function_name/1
смысле.Довольно странно привыкать к сокращенным определениям функций (func (& 1) и т. Д.), Но удобно, когда вы пытаетесь передать или сохранить ваш код сжатым.
источник
Ты можешь сделать
otherfunction(&myfunction/2)
Поскольку elixir может выполнять функции без скобок (например
myfunction
),otherfunction(myfunction))
его использование будет пытаться выполнитьmyfunction/0
.Итак, вам нужно использовать оператор захвата и указать функцию, включая arity, поскольку у вас могут быть разные функции с одним и тем же именем. Таким образом,
&myfunction/2
.источник