web-dev-qa-db-ja.com

Elixirに2種類の関数があるのはなぜですか?

私はElixirを学んでいますが、なぜ2種類の関数定義があるのか​​疑問に思います:

  • myfunction(param1, param2)を使用して呼び出されるdefでモジュールに定義された関数
  • myfn.(param1, param2)を使用して呼び出されるfnで定義された匿名関数

2種類目の関数のみがファーストクラスのオブジェクトのようであり、他の関数にパラメーターとして渡すことができます。モジュールで定義された関数は、fnでラップする必要があります。それを簡単にするためにotherfunction(myfunction(&1, &2))のような構文糖がありますが、そもそもなぜそれが必要なのでしょうか?なぜotherfunction(myfunction))できないのですか? Rubyのように括弧なしでモジュール関数の呼び出しを許可するだけですか?モジュールの機能と機能を備えたErlangからこの特性を継承しているようですが、実際にはErlang VMが内部でどのように機能するのでしょうか?

2種類の関数を持ち、それらを他の関数に渡すために1つの型から別の型に変換する利点はありますか?関数を呼び出すために2つの異なる表記法があるという利点はありますか?

262
Alex Marandon

命名を明確にするために、両方とも機能です。 1つは名前付き関数で、もう1つは匿名関数です。しかし、あなたは正しい、彼らはやや異なって動作し、私は彼らがそのように動作する理由を説明するつもりです。

2番目のfnから始めましょう。 fnはクロージャーで、Rubyのlambdaに似ています。次のように作成できます。

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

このコードは、defが表示されるたびに空の変数スコープを取得するため、コンパイルに失敗します。それはそれらの間の重要な違いです。私は特に、各名前付き関数がクリーンな状態で始まり、異なるスコープの変数がすべて混同されないという事実が好きです。明確な境界があります。

上記の名前付きhello関数を匿名関数として取得できます。あなたはそれを自分で言った:

other_function(&hello(&1))

そして、あなたは、なぜ他の言語のように単純にhelloとして渡すことができないのかと尋ねました。これは、Elixirの関数が名前and arityで識別されるためです。したがって、2つの引数を必要とする関数は、同じ名前であっても、3つの引数を必要とする関数とは異なります。したがって、単にhelloを渡した場合、どのhelloが実際に意味するのかわかりません。引数が2つ、3つ、または4つあるのでしょうか?これは、アリティが異なる句を持つ無名関数を作成できない理由とまったく同じです。

Elixir v0.10.1以降、名前付き関数をキャプチャする構文があります。

&hello/1

これにより、ローカルの名前付き関数helloがアリティ1でキャプチャされます。言語とそのドキュメント全体で、このhello/1構文で関数を識別することは非常に一般的です。

これは、Elixirが匿名関数を呼び出すためにドットを使用する理由でもあります。単にhelloを関数として渡すことができないため、代わりに明示的にキャプチャする必要があるため、名前付き関数と匿名関数には自然な区別があり、それぞれを呼び出すための明確な構文はすべてをより明確にします(Lispers LISP 1対LISP 2の議論により、これに精通しています。

全体として、これらが2つの機能を備えている理由と、それらの動作が異なる理由です。

361
José Valim

これが他の人にとってどれほど役に立つかはわかりませんが、最終的に概念に頭を包んだのは、エリクサー関数が関数ではないことを認識することでした。

エリクサーのすべては表現です。そう

MyModule.my_function(foo) 

関数ではなく、my_functionのコードを実行して返される式です。実際には、引数として渡すことができる「関数」を取得する方法は1つしかなく、それは匿名関数表記を使用することです。

Fnまたは&表記を関数ポインターとして参照するのは魅力的ですが、実際にはそれ以上です。それは周囲の環境の閉鎖です。

自問する場合:

この場所に実行環境またはデータ値が必要ですか?

また、fnを使用して実行する必要がある場合は、ほとんどの困難がより明確になります。

誰も言及していないので間違っているかもしれませんが、この理由は、括弧なしで関数を呼び出すことができるRubyの遺産でもあるという印象を受けていました。

アリティは明らかに関係していますが、しばらくの間それを脇に置き、引数なしで関数を使用してみましょう。括弧が必須のjavascriptのような言語では、関数を引数として渡すことと関数を呼び出すことを簡単に区別できます。ブラケットを使用する場合にのみ呼び出します。

my_function // argument
(function() {}) // argument

my_function() // function is called
(function() {})() // function is called

ご覧のとおり、名前を付けても付けなくても、大きな違いはありません。しかし、elixirとRubyを使用すると、角括弧なしで関数を呼び出すことができます。これは私が個人的に好きなデザインの選択ですが、この副作用があります。関数を呼び出すことを意味する可能性があるため、括弧なしで名前だけを使用することはできません。これが&の目的です。 arity appartをしばらく放置する場合、&を関数名の前に付けると、この関数が返すものではなく、この関数を引数として明示的に使用することを意味します。

無名関数は、主に引数として使用されるという点で少し異なります。繰り返しになりますが、これは設計上の選択ですが、その背後にある合理的な理由は、関数を引数としてとるイテレーターの種類の関数によって主に使用されることです。したがって、デフォルトですでに引数と見なされているため、明らかに&を使用する必要はありません。それが彼らの目的です。

最後の問題は、イテレータの種類の関数で常に使用されるとは限らないか、自分でイテレータをコーディングしている可能性があるため、コードで呼び出す必要がある場合があることです。小さな話では、Rubyはオブジェクト指向であるため、そのための主な方法はオブジェクトでcallメソッドを使用することでした。そうすれば、必須ではないブラケットの動作の一貫性を保つことができます。

my_lambda.call
my_lambda.call()
my_lambda_with_arguments.call :h2g2, 42
my_lambda_with_arguments.call(:h2g2, 42)

今、誰かが基本的に名前のないメソッドのように見えるショートカットを思いつきました。

my_lambda.()
my_lambda_with_arguments.(:h2g2, 42)

繰り返しますが、これは設計上の選択です。現在、エリクサーはオブジェクト指向ではないため、最初のフォームを使用しないでください。私はホセのことを話すことはできませんが、2番目の形式はまだ余分な文字を含む関数呼び出しのように見えるため、エリキシルで使用されたようです。関数呼び出しに十分近いです。

すべての長所と短所については考えませんでしたが、両方の言語で、匿名関数にブラケットを必須にする限り、ブラケットだけで逃げることができるようです。それは次のようです:

必須括弧VSわずかに異なる表記

どちらの場合も、両方の動作が異なるため、例外を作成します。違いがあるので、それを明らかにして、異なる表記法を採用することもできます。必須の括弧はほとんどの場合自然に見えますが、物事が計画通りに進まない場合は非常に混乱します。

どうぞ。今、私は詳細のほとんどを単純化したので、これは世界で最高の説明ではないかもしれません。また、そのほとんどはデザインの選択であり、私はそれらを判断せずにそれらの理由を説明しようとしました。私はエリクサーが好きで、Rubyが大好きです。括弧のない関数呼び出しが好きですが、あなたのように、結果がたまに誤解を招くことがあります。

そしてエリクサーでは、これはちょうどこの余分なドットですが、Rubyではこれの上にブロックがあります。ブロックはすばらしく、ブロックだけでどれだけできるか驚いていますが、最後の引数である匿名関数が1つだけ必要な場合にのみ機能します。次に、他のシナリオに対処できるはずなので、method/lambda/proc/blockの混乱全体がここに来ます。

とにかく...これは範囲外です。

11
Mig R

これの説明がなぜそんなに複雑なのか理解できませんでした。

それは本当に、Rubyスタイルの「括弧なしの関数実行」の現実と組み合わされた、非常に小さな違いです。

比較する:

def fun1(x, y) do
  x + y
end

に:

fun2 = fn
  x, y -> x + y
end

これらは両方とも単なる識別子ですが...

  • fun1は、defで定義された名前付き関数を記述する識別子です。
  • fun2は、変数(関数への参照を含む)を記述する識別子です。

他の式にfun1またはfun2が表示される場合、それが何を意味するかを考えてください。その式を評価するとき、参照される関数を呼び出しますか、それともメモリから値を参照するだけですか?

コンパイル時に知る良い方法はありません。 Rubyには、変数バインディングが何らかの時点で関数をシャドウイングしているかどうかを調べるために、変数ネームスペースを内省する贅沢があります。コンパイルされているElixirは、実際にはこれを行うことができません。それがドット表記法の機能です。Elixirに関数参照を含める必要があり、呼び出す必要があることを伝えます。

そして、これは本当に難しいです。ドット表記がなかったと想像してください。次のコードを検討してください。

val = 5

if :Rand.uniform < 0.5 do
  val = fn -> 5 end
end

IO.puts val     # Does this work?
IO.puts val.()  # Or maybe this?

上記のコードを考えると、なぜElixirにヒントを与えなければならないのかは明らかだと思います。すべての変数の逆参照が関数をチェックしなければならないと想像してみてください?あるいは、変数の逆参照が関数を使用していると常に推測するために必要なヒロイックを想像してください。

10
Jayson

この動作に関する優れたブログ投稿があります: link

2種類の機能

モジュールにこれが含まれる場合:

fac(0) when N > 0 -> 1;
fac(N)            -> N* fac(N-1).

これを切り取ってシェルに貼り付けるだけでは、同じ結果を得ることができません。

Erlangにバグがあるためです。 Erlangのモジュールは、形式のシーケンスです。 Erlangシェルは、一連のを評価します。 Erlangでは形式ではありません。

double(X) -> 2*X.            in an Erlang module is a FORM

Double = fun(X) -> 2*X end.  in the Shell is an EXPRESSION

2つは同じではありません。この愚かさは永遠にアーランでしたが、私たちはそれに気付かず、それと一緒に暮らすことを学びました。

fnを呼び出す際のドット

iex> f = fn(x) -> 2 * x end
#Function<erl_eval.6.17052888>
iex> f.(10)
20

学校では、f。(10)ではなくf(10)と書くことで関数を呼び出すことを学びました。これは、Shell.f(10)のような名前を持つ関数です(これは、シェル)シェル部分は暗黙的であるため、f(10)と呼ぶ必要があります。

このまま放置する場合は、人生の次の20年間でその理由を説明してください。

8
sobolevn

fn ->構文は、匿名関数を使用するためのものです。 var。()を実行することは、関数を保持しているものとしてvarを参照する代わりに、funcを含むvarを取得して実行することをelixirに伝えるだけです。

Elixirにはこの共通パターンがあり、関数内に何かを実行する方法を確認するためのロジックを持たせる代わりに、入力の種類に基づいて異なる関数をパターンマッチングします。これが、function_name/1の意味でアリティによって物事を参照する理由です。

簡単な関数定義(func(&1)など)を行うことに慣れるのはちょっと変ですが、コードを簡潔にしたり、パイプしたりする場合に便利です。

0
Ian

2種類目の関数のみがファーストクラスのオブジェクトであるように見え、他の関数にパラメーターとして渡すことができます。モジュールで定義された関数は、fnでラップする必要があります。それを簡単にするためにotherfunction(myfunction(&1, &2))のような構文糖がありますが、そもそもなぜそれが必要なのでしょうか?なぜotherfunction(myfunction))できないのですか?

otherfunction(&myfunction/2)できます

Elixirは角かっこなしで(myfunctionなど)関数を実行できるため、otherfunction(myfunction))を使用してmyfunction/0を実行しようとします。

したがって、キャプチャ演算子を使用して、アリティを含む関数を指定する必要があります。同じ名前の異なる関数を使用できるためです。したがって、&myfunction/2

0
ryanwinchester