Um die Benennung zu verdeutlichen, sind beide Funktionen. Eine ist eine benannte Funktion und die andere ist eine anonyme. Aber Sie haben Recht, sie funktionieren etwas anders und ich werde veranschaulichen, warum sie so funktionieren.
Beginnen wir mit der zweiten , fn
. fn
ist ein Verschluss, ähnlich einem lambda
in Ruby. Wir können es wie folgt erstellen:
x = 1
fun = fn y -> x + y end
fun.(2) #=> 3
Eine Funktion kann auch mehrere Klauseln enthalten:
x = 1
fun = fn
y when y < 0 -> x - y
y -> x + y
end
fun.(2) #=> 3
fun.(-2) #=> 3
Versuchen wir jetzt etwas anderes. Versuchen wir, verschiedene Klauseln zu definieren, die eine unterschiedliche Anzahl von Argumenten erwarten:
fn
x, y -> x + y
x -> x
end
** (SyntaxError) cannot mix clauses with different arities in function definition
Ach nein! Wir bekommen einen Fehler! Wir können keine Klauseln mischen, die eine andere Anzahl von Argumenten erwarten. Eine Funktion hat immer eine feste Arität.
Lassen Sie uns nun über die genannten Funktionen sprechen:
def hello(x, y) do
x + y
end
Wie erwartet haben sie einen Namen und können auch einige Argumente erhalten. Sie sind jedoch keine Schließungen:
x = 1
def hello(y) do
x + y
end
Dieser Code kann nicht kompiliert werden, da jedes Mal, wenn Sie einen sehen def
, ein leerer Variablenbereich angezeigt wird. Das ist ein wichtiger Unterschied zwischen ihnen. Mir gefällt besonders die Tatsache, dass jede benannte Funktion mit einer sauberen Tabelle beginnt und Sie nicht die Variablen verschiedener Bereiche miteinander verwechseln. Sie haben eine klare Grenze.
Wir könnten die oben genannte Hallo-Funktion als anonyme Funktion abrufen. Sie haben es selbst erwähnt:
other_function(&hello(&1))
Und dann hast du gefragt, warum ich es nicht einfach so weitergeben kann hello
wie in anderen Sprachen? Das liegt daran, dass Funktionen in Elixir durch Name und Arität identifiziert werden . Eine Funktion, die zwei Argumente erwartet, ist also eine andere Funktion als eine, die drei erwartet, selbst wenn sie denselben Namen haben. Wenn wir also einfach bestanden hätten hello
, hätten wir keine Ahnung, was hello
Sie eigentlich gemeint haben. Der mit zwei, drei oder vier Argumenten? Dies ist genau der gleiche Grund, warum wir keine anonyme Funktion mit Klauseln mit unterschiedlichen Aritäten erstellen können.
Seit Elixir v0.10.1 haben wir eine Syntax zum Erfassen benannter Funktionen:
&hello/1
Dadurch wird die lokal benannte Funktion Hallo mit Arität 1 erfasst. In der gesamten Sprache und ihrer Dokumentation ist es sehr häufig, Funktionen in dieser hello/1
Syntax zu identifizieren .
Aus diesem Grund verwendet Elixir auch einen Punkt zum Aufrufen anonymer Funktionen. Da Sie nicht einfach hello
als Funktion weitergeben können, sondern diese explizit erfassen müssen, gibt es eine natürliche Unterscheidung zwischen benannten und anonymen Funktionen, und eine unterschiedliche Syntax für den Aufruf macht jede Funktion etwas expliziter (Lispers wäre damit vertraut aufgrund der Diskussion zwischen Lisp 1 und Lisp 2).
Insgesamt sind dies die Gründe, warum wir zwei Funktionen haben und warum sie sich unterschiedlich verhalten.
f()
(ohne Punkt) funktionieren könnte .is_function/2
eines Schutzes abgleichen.is_function(f, 2)
prüft,SomeFun()
undsome_fun()
. Wenn wir in Elixir den Punkt entfernen, sind sie dieselbensome_fun()
undsome_fun()
weil Variablen denselben Bezeichner wie Funktionsnamen verwenden. Daher der Punkt.Ich weiß nicht, wie nützlich dies für andere sein wird, aber ich habe mich schließlich mit dem Konzept beschäftigt, um zu erkennen, dass Elixierfunktionen keine Funktionen sind.
Alles im Elixier ist ein Ausdruck. Damit
ist keine Funktion, sondern der Ausdruck, der durch Ausführen des Codes in zurückgegeben wird
my_function
. Es gibt eigentlich nur einen Weg, eine "Funktion" zu erhalten, die Sie als Argument weitergeben können, nämlich die anonyme Funktionsnotation zu verwenden.Es ist verlockend, die fn- oder & -Notation als Funktionszeiger zu bezeichnen, aber es ist tatsächlich viel mehr. Es ist eine Schließung der Umgebung.
Wenn Sie sich fragen:
Benötige ich an dieser Stelle eine Ausführungsumgebung oder einen Datenwert?
Und wenn Sie die Ausführung mit fn benötigen, werden die meisten Schwierigkeiten viel deutlicher.
quelle
MyModule.my_function(foo)
ein Ausdruck ist, aberMyModule.my_function
"könnte" ein Ausdruck gewesen sein, der eine Funktion "Objekt" zurückgibt. Aber da Sie die Arität erzählen müssen, würden SieMyModule.my_function/1
stattdessen so etwas brauchen . Und ich denke, dass sie entschieden haben, dass es besser ist,&MyModule.my_function(&1)
stattdessen eine Syntax zu verwenden, die es ermöglicht, die Arität auszudrücken (und auch anderen Zwecken dient). Noch unklar, warum es einen()
Operator für benannte Funktionen und einen.()
Operator für unbenannte Funktionen gibt&MyModule.my_function/1
So können Sie es als Funktion weitergeben.Ich habe nie verstanden, warum Erklärungen dafür so kompliziert sind.
Es ist wirklich nur eine außergewöhnlich kleine Unterscheidung, kombiniert mit den Realitäten der "Funktionsausführung ohne Parens" im Ruby-Stil.
Vergleichen Sie:
Zu:
Während beide nur Bezeichner sind ...
fun1
ist eine Kennung, die eine benannte Funktion beschreibt, die mit definiert istdef
.fun2
ist ein Bezeichner, der eine Variable beschreibt (die zufällig einen Verweis auf eine Funktion enthält).Überlegen Sie, was das bedeutet, wenn Sie
fun1
oderfun2
in einem anderen Ausdruck sehen? Rufen Sie bei der Auswertung dieses Ausdrucks die referenzierte Funktion auf oder verweisen Sie nur auf einen Wert aus dem Speicher?Es gibt keine gute Möglichkeit, dies beim Kompilieren zu wissen. Ruby hat den Luxus, den Variablennamensraum zu überprüfen, um herauszufinden, ob eine Variablenbindung zu einem bestimmten Zeitpunkt eine Funktion beschattet hat. Elixir, das kompiliert wird, kann das nicht wirklich. Das macht die Punktnotation, sie sagt Elixir, dass sie eine Funktionsreferenz enthalten soll und dass sie aufgerufen werden soll.
Und das ist wirklich schwer. Stellen Sie sich vor, es gäbe keine Punktnotation. Betrachten Sie diesen Code:
Angesichts des obigen Codes denke ich, dass es ziemlich klar ist, warum Sie Elixir den Hinweis geben müssen. Stellen Sie sich vor, jede Variablen-Referenzierung müsste nach einer Funktion suchen? Stellen Sie sich alternativ vor, welche Heldentaten notwendig wären, um immer darauf zu schließen, dass die variable Dereferenzierung eine Funktion verwendet?
quelle
Ich kann mich irren, da niemand es erwähnt hat, aber ich hatte auch den Eindruck, dass der Grund dafür auch das rubinrote Erbe ist, Funktionen ohne Klammern aufrufen zu können.
Arity ist offensichtlich involviert, aber lassen Sie es uns für eine Weile beiseite legen und Funktionen ohne Argumente verwenden. In einer Sprache wie Javascript, in der Klammern obligatorisch sind, ist es einfach, den Unterschied zwischen der Übergabe einer Funktion als Argument und dem Aufruf der Funktion zu unterscheiden. Sie rufen es nur auf, wenn Sie die Klammern verwenden.
Wie Sie sehen können, macht es keinen großen Unterschied, ob Sie es benennen oder nicht. Mit Elixier und Rubin können Sie jedoch Funktionen ohne Klammern aufrufen. Dies ist eine Designauswahl, die mir persönlich gefällt, aber sie hat den Nebeneffekt, dass Sie nicht nur den Namen ohne die Klammern verwenden können, da dies bedeuten könnte, dass Sie die Funktion aufrufen möchten. Dafür ist das da
&
. Wenn Sie arity appart für eine Sekunde verlassen,&
bedeutet das Voranstellen Ihres Funktionsnamens , dass Sie diese Funktion explizit als Argument verwenden möchten und nicht, was diese Funktion zurückgibt.Jetzt ist die anonyme Funktion etwas anders, da sie hauptsächlich als Argument verwendet wird. Auch dies ist eine Entwurfsentscheidung, aber das Rationale dahinter ist, dass sie hauptsächlich von Iteratorfunktionen verwendet wird, die Funktionen als Argumente verwenden. Sie müssen sie also offensichtlich nicht verwenden,
&
da sie standardmäßig bereits als Argumente betrachtet werden. Es ist ihr Zweck.Das letzte Problem ist, dass Sie sie manchmal in Ihrem Code aufrufen müssen, weil sie nicht immer mit einer Iteratorfunktion verwendet werden, oder Sie codieren möglicherweise selbst einen Iterator. Da Ruby objektorientiert ist, bestand die Hauptmethode für die kleine Geschichte darin, die
call
Methode für das Objekt zu verwenden. Auf diese Weise können Sie das Verhalten der nicht obligatorischen Klammern konsistent halten.Jetzt hat sich jemand eine Verknüpfung ausgedacht, die im Grunde wie eine Methode ohne Namen aussieht.
Auch dies ist eine Design-Wahl. Jetzt ist Elixier nicht objektorientiert und ruft daher sicher nicht die erste Form auf. Ich kann nicht für José sprechen, aber es sieht so aus, als ob die zweite Form in Elixier verwendet wurde, da sie immer noch wie ein Funktionsaufruf mit einem zusätzlichen Zeichen aussieht. Es ist nah genug an einem Funktionsaufruf.
Ich habe nicht über alle Vor- und Nachteile nachgedacht, aber es sieht so aus, als könnten Sie in beiden Sprachen nur mit den Klammern davonkommen, solange Sie Klammern für anonyme Funktionen obligatorisch machen. Es scheint so zu sein:
Obligatorische Klammern VS Etwas andere Notation
In beiden Fällen machen Sie eine Ausnahme, weil sich beide unterschiedlich verhalten. Da es einen Unterschied gibt, können Sie ihn genauso gut deutlich machen und sich für die andere Notation entscheiden. Die obligatorischen Klammern sehen in den meisten Fällen natürlich aus, sind aber sehr verwirrend, wenn die Dinge nicht wie geplant verlaufen.
Bitte schön. Dies ist möglicherweise nicht die beste Erklärung der Welt, da ich die meisten Details vereinfacht habe. Das meiste davon sind auch Designentscheidungen, und ich habe versucht, einen Grund dafür anzugeben, ohne sie zu beurteilen. Ich liebe Elixier, ich liebe Rubin, ich mag die Funktionsaufrufe ohne Klammern, aber wie Sie finde ich die Konsequenzen hin und wieder ziemlich irreführend.
Und in Elixier ist es nur dieser zusätzliche Punkt, während in Rubin Blöcke darüber liegen. Blöcke sind erstaunlich und ich bin überrascht, wie viel Sie mit nur Blöcken tun können, aber sie funktionieren nur, wenn Sie nur eine anonyme Funktion benötigen, die das letzte Argument ist. Da Sie dann in der Lage sein sollten, mit anderen Szenarien umzugehen, kommt hier die gesamte Methode / Lambda / Proc / Block-Verwirrung.
Wie auch immer ... das ist nicht möglich.
quelle
Es gibt einen ausgezeichneten Blog-Beitrag zu diesem Verhalten: Link
Zwei Arten von Funktionen
Punkt beim Anrufen
fn
quelle
Elixir verfügt optional über geschweifte Klammern für Funktionen, einschließlich Funktionen mit 0 arity. Sehen wir uns ein Beispiel an, warum eine separate Aufrufsyntax wichtig ist:
Ohne einen Unterschied zwischen zwei Arten von Funktionen zu machen, können wir nicht sagen, was
Insanity.dive
bedeutet: eine Funktion selbst abrufen, aufrufen oder auch die resultierende anonyme Funktion aufrufen.quelle
fn ->
Die Syntax dient zur Verwendung anonymer Funktionen. Wenn Sie var. () Ausführen, wird Elixir nur gesagt, dass Sie diese var mit einer Funktion darin ausführen und ausführen sollen, anstatt die var als etwas zu bezeichnen, das nur diese Funktion enthält.Elixir hat dieses gemeinsame Muster, bei dem anstelle einer Logik in einer Funktion, um zu sehen, wie etwas ausgeführt werden soll, verschiedene Funktionen basierend auf der Art der Eingabe, die wir haben, mit Mustern übereinstimmen. Ich nehme an, deshalb beziehen wir uns auf Dinge durch Arität im
function_name/1
Sinne.Es ist etwas seltsam, sich an Kurzfunktionsdefinitionen (func (& 1) usw.) zu gewöhnen, aber praktisch, wenn Sie versuchen, Ihren Code zu leiten oder kurz zu halten.
quelle
Du kannst tun
otherfunction(&myfunction/2)
Da elixir Funktionen ohne die Klammern (wie
myfunction
) ausführen kann,otherfunction(myfunction))
wird versucht, es auszuführenmyfunction/0
.Sie müssen also den Erfassungsoperator verwenden und die Funktion einschließlich arity angeben, da Sie verschiedene Funktionen mit demselben Namen haben können. Also ,
&myfunction/2
.quelle