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


279

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

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

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

2つのタイプの関数を持ち、それらを他の関数に渡すためにあるタイプから別のタイプに変換する利点はありますか?関数を呼び出すための2つの異なる表記法がある利点はありますか?

回答:


386

名前を明確にするために、どちらも機能です。1つは名前付き関数で、もう1つは匿名関数です。しかし、あなたは正しい、それらは多少異なる動作をするので、なぜそれらがそのように動作するのかを説明します。

2番目のから始めましょうfn。Rubyのにfn似たクロージャー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他の言語のようにそれを単に渡すことができないのですか?それは、エリクサーの関数が名前アリティによって識別されるためです。したがって、2つの引数が必要な関数は、同じ名前であっても、3つが必要な関数とは異なります。したがって、単にを渡しただけではhellohello実際に何を意味するのかわかりません。2つ、3つ、または4つの引数を持つもの?これは、異なるアリティの句を持つ無名関数を作成できないのと同じ理由です。

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

&hello/1

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

これは、エリクサーが匿名関数を呼び出すためにドットを使用する理由でもあります。hello関数として渡すだけではなく、明示的にキャプチャする必要があるため、名前付き関数と匿名関数には自然な違いがあり、それぞれを呼び出すための明確な構文により、すべてが少し明示的になります(Lispersはこれに精通しています) Lisp 1対Lisp 2の議論による)。

全体として、これらが、2つの機能があり、動作が異なる理由です。


30
私はElixirも学習していますが、これは私が遭遇した最初の問題で、一時停止をもたらしました。素晴らしい説明ですが、明確にするために...これは実装の問題の結果ですか、それとも関数の使用と受け渡しに関する深い知識を反映していますか?匿名関数は引数の値に基づいて照合できるため、引数の数も照合できると便利です(他の場所での関数のパターンマッチングと一致します)。
サイクロン

17
これはf()、ドットなしでも機能するという意味では実装上の制約ではありません。
ホセ・Valim

6
is_function/2ガードで使用することにより、引数の数を照合できます。is_function(f, 2)チェックそれは:) 2のアリティを持っている
ホセ・Valim

20
名前付き関数と匿名関数の両方で、ドットなしの関数呼び出しを優先しました。混乱を招き、特定の関数が匿名であるか名前付きであるかを忘れてしまいます。また、カレーに関しては、より多くのノイズがあります。
CMCDragonkai 14

28
Erlangでは、匿名関数呼び出しと通常の関数はすでに構文的に異なります:SomeFun()some_fun()。Elixirでは、ドットを削除した場合、それらは同じにsome_fun()なりsome_fun()ます。変数は関数名と同じ識別子を使用するためです。したがって、ドット。
ホセ・Valim

17

これが他の人にとってどれほど役立つかはわかりませんが、最終的にコンセプトに頭を抱える方法は、エリクサー関数が関数ではないことを理解することでした。

エリキシルのすべては表現です。そう

MyModule.my_function(foo) 

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

fnまたは&表記を関数ポインターとして参照するのは魅力的ですが、実際にはそれよりずっと多くなります。それは周囲の環境の閉鎖です。

自問する場合:

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

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


2
それMyModule.my_function(foo)が式であることは明らかですが、MyModule.my_function「できる」は関数「オブジェクト」を返す式でした。しかし、アリティを伝える必要があるため、MyModule.my_function/1代わりに次のようなものが必要になります。そして&MyModule.my_function(&1) 、アリティを表現できる構文を使用する方が良いと判断したと思います(他の目的にも役立ちます)。まだ不明であり、なぜ()という名前の関数の演算子と.()無名の機能のための演算子は
RubenLaguna

私はあなたが望むと思う:&MyModule.my_function/1それはあなたがそれを関数として渡すことができる方法です。
jnmandal 2018

14

これの説明がなぜそれほど複雑なのか、私には理解できません。

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

比較:

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

に:

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

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

  • fun1で定義された名前付き関数を説明する識別子ですdef
  • fun2 変数を説明する識別子です(たまたま関数への参照が含まれています)。

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

コンパイル時に知る良い方法はありません。Rubyには、変数の名前空間をイントロスペクトして、変数バインディングが特定の時点で関数を隠していないかどうかを調べるという贅沢があります。コンパイルされているエリクサーは、これを実際に行うことはできません。これがドット表記の機能であり、関数参照が含まれている必要があり、呼び出される必要があることを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にヒントを与える必要があるのか​​は明らかです。すべての変数の逆参照が関数をチェックしなければならなかったと想像してみてください。または、変数の逆参照が関数を使用していると常に推測するには、どの英雄が必要かを想像してみてください。


12

誰もそれを言わなかったので私は間違っているかもしれませんが、この理由は、括弧なしで関数を呼び出すことができるというルビーの伝統でもあるという印象も受けました。

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

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

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

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

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

最後の問題は、イテレーターのような関数で常に使用されるとは限らないため、またはコード内でイテレーターをコーディングしている可能性があるため、コード内で呼び出す必要がある場合があることです。ささいな話として、ルビーはオブジェクト指向であるため、それを行う主な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わずかに異なる表記

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

どうぞ。詳細のほとんどを簡略化したので、これは世界で最も良い説明ではないかもしれません。また、そのほとんどはデザインの選択であり、私はそれらを判断せずにそれらに理由を与えようとしました。私はエリキシルが好きで、ルビーが好きです。角括弧なしの関数呼び出しが好きですが、あなたのように、結果がたまに誤解することがあります。

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

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


9

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

2種類の機能

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

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

これをシェルにカットアンドペーストして同じ結果を得ることはできません。

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

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

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

2つは同じではありません。このちょっとした愚かさは永遠にErlangにありましたが、私たちはそれに気づかず、私はそれと共存することを学びました。

呼び出し中のドット 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年間を理由の説明に費やすことになります。


OPの質問に直接回答することの有用性はわかりませんが、提供されたリンク(コメントセクションを含む)は、質問の対象読者、つまりElixir + Erlangの初心者および学習者にとって、実際には優れたIMOです。 。

リンクは今死んでいます:(
AnilRedshift

2

Elixirには、アリティが0の関数を含む、関数のオプションのブレースがあります。個別の呼び出し構文が重要になる理由の例を見てみましょう。

defmodule Insanity do
  def dive(), do: fn() -> 1 end
end

Insanity.dive
# #Function<0.16121902/0 in Insanity.dive/0>

Insanity.dive() 
# #Function<0.16121902/0 in Insanity.dive/0>

Insanity.dive.()
# 1

Insanity.dive().()
# 1

2種類の関数を区別しないとInsanity.dive、関数の取得、呼び出し、または結果として得られる無名関数の呼び出しの意味がわかりません。


1

fn ->構文は無名関数を使用するためのものです。var。()を実行することは、その関数を保持しているものとしてvarを参照するのではなく、funcを含むそのvarを取得して実行することをelixirに指示するだけです。

Elixirにはこのような一般的なパターンがあり、関数の内部にロジックがあり、何かがどのように実行されるかを確認するのではなく、入力の種類に基づいてさまざまな関数をパターンマッチングします。これが、あるfunction_name/1意味でアリティによって物事を指す理由だと思います。

関数の省略表現(func(&1)など)に慣れるのは奇妙なことですが、コードをパイプ処理したり、コードを簡潔にしたりする場合に便利です。


0

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

できるよ otherfunction(&myfunction/2)

エリクサーは角括弧なしで(などmyfunction)関数を実行できるため、これを使用otherfunction(myfunction))するとが実行されmyfunction/0ます。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.