Syntaxdesign - Warum Klammern verwenden, wenn keine Argumente übergeben werden?

66

In vielen Sprachen wird die Syntax function_name(arg1, arg2, ...)zum Aufrufen einer Funktion verwendet. Wenn wir die Funktion ohne Argumente aufrufen wollen, müssen wir tun function_name().

Ich finde es seltsam, dass ein Compiler oder ein ()Skriptinterpreter dies als Funktionsaufruf erfolgreich erkennen müsste . Wenn bekannt ist, dass eine Variable aufrufbar ist, warum würde das nicht function_name;ausreichen?

Auf der anderen Seite können wir in einigen Sprachen: function_name 'test';oder sogar function_name 'first' 'second';eine Funktion oder einen Befehl aufrufen.

Ich denke, Klammern wären besser gewesen, wenn sie nur zur Angabe der Prioritätsreihenfolge benötigt worden wären, und an anderen Stellen wären sie optional. Beispielsweise sollte doing if expression == true function_name;so gültig sein wie if (expression == true) function_name();.

Das Ärgerlichste ist meiner Meinung nach, 'SOME_STRING'.toLowerCase()wenn die Prototypfunktion eindeutig keine Argumente benötigt. Warum haben sich die Designer gegen das Einfachere entschieden 'SOME_STRING'.lower?

Haftungsausschluss: Verstehen Sie mich nicht falsch, ich liebe die C-ähnliche Syntax! ;) Ich frage nur, ob es besser sein könnte. Hat das Erfordernis ()Leistungsvorteile oder erleichtert es das Verständnis des Codes? Ich bin sehr gespannt, was genau der Grund ist.

David Refoua
quelle
103
Was würden Sie tun, wenn Sie eine Funktion als Argument an eine andere Funktion übergeben möchten?
Vincent Savard
30
Solange Code von Menschen gelesen werden muss, ist die Lesbarkeit von entscheidender Bedeutung.
Tulains Córdova
75
Das ist die Sache mit der Lesbarkeit, nach der Sie fragen (), aber das, was in Ihrem Beitrag auffällt, ist die if (expression == true)Aussage. Sie sorgen sich um überflüssige (), aber dann verwenden Sie eine überflüssige == true:)
David Arno
15
Visual Basic erlaubt dies
edc65
14
In Pascal war dies obligatorisch. Sie haben parens nur für Funktionen und Prozeduren verwendet, für die Argumente erforderlich waren.
RemcoGerlich

Antworten:

251

Für Sprachen, die erstklassige Funktionen verwenden , ist es durchaus üblich, dass die Syntax für den Verweis auf eine Funktion wie folgt lautet:

a = object.functionName

während der Akt des Aufrufs dieser Funktion ist:

b = object.functionName()

aIm obigen Beispiel würde auf die obige Funktion verwiesen (und Sie könnten sie durch Ausführen aufrufen a()), während bder Rückgabewert der Funktion enthalten würde.

Während einige Sprachen Funktionsaufrufe ohne Klammern ausführen können, kann es verwirrend werden, ob sie die Funktion aufrufen oder einfach auf die Funktion verweisen .

Nathan Merrill
quelle
9
Vielleicht möchten Sie erwähnen, dass dieser Unterschied nur bei Vorhandensein von Nebenwirkungen interessant ist - für eine reine Funktion spielt es keine Rolle
Bergi
73
@Bergi: Es ist sehr wichtig für reine Funktionen! Wenn ich eine Funktion habe make_list, die ihre Argumente in eine Liste packt, gibt es einen großen Unterschied zwischen der f(make_list)Übergabe einer Funktion an feine leere Liste.
user2357112
12
@Bergi: Selbst dann möchte ich immer noch in der Lage sein, SATInstance.solveeine unglaublich teure reine Funktion an eine andere Funktion zu übergeben, ohne sofort zu versuchen, sie auszuführen. Es macht die Dinge auch sehr umständlich, wenn someFunctionetwas anders ist, je nachdem, ob someFunctiones als rein deklariert wird. Zum Beispiel, wenn ich (Pseudo - Code) haben func f() {return g}, func g() {doSomethingImpure}und func apply(x) {x()}dann apply(f)ruft f. Wenn ich dann erklären , dass frein ist , dann plötzlich apply(f)geht gauf apply, und applyAnrufe gund tut etwas unrein.
user2357112
6
@ user2357112 Die Bewertungsstrategie ist unabhängig von der Reinheit. Verwenden Sie die Syntax von Aufrufen als Annotation, um zu bewerten, was möglich (aber wahrscheinlich umständlich) ist. Dies ändert jedoch nichts am Ergebnis oder an der Richtigkeit. In Bezug auf Ihr Beispiel apply(f), wenn ges unrein ist (und applyauch?), Dann ist das Ganze unrein und bricht natürlich zusammen.
Bergi
14
Beispiele hierfür sind Python und ECMAScript, die beide nicht wirklich über ein Kernkonzept von "Methoden" verfügen. Stattdessen verfügen Sie über eine named-Eigenschaft, die ein anonymes erstklassiges Funktionsobjekt zurückgibt, und rufen dann diese anonyme erstklassige Funktion auf. Insbesondere wird in Python und ECMAScript foo.bar()nicht als eine Operation " barObjektmethode aufrufen foo", sondern als zwei Operationen " barObjektfeld dereferenzieren foo und dann das von diesem Feld zurückgegebene Objekt aufrufen" angegeben. Das .ist also der Feldauswahl-Operator und ()der Anruf-Operator, und sie sind unabhängig.
Jörg W. Mittag
63

In der Tat erlaubt Scala dies, obwohl es eine Konvention gibt, die befolgt wird: Wenn die Methode Nebenwirkungen hat, sollten trotzdem Klammern verwendet werden.

Als Compiler-Autor empfinde ich das garantierte Vorhandensein von Klammern als recht praktisch. Ich würde immer wissen, dass dies ein Methodenaufruf ist, und ich müsste für den einen oder anderen Fall keine Gabelung einbauen.

Als Programmierer und Codeleser lässt das Vorhandensein von Klammern keinen Zweifel daran, dass es sich um einen Methodenaufruf handelt, obwohl keine Parameter übergeben werden.

Die Übergabe von Parametern ist nicht das einzige bestimmende Merkmal eines Methodenaufrufs. Warum sollte ich eine parameterlose Methode anders behandeln als eine Methode mit Parametern?

Robert Harvey
quelle
Vielen Dank, und Sie haben stichhaltige Gründe angegeben, warum dies wichtig ist. Können Sie auch einige Beispiele nennen, warum Sprachdesigner Funktionen in Prototypen verwenden sollten?
David Refoua
Das Vorhandensein von Klammern lässt keinen Zweifel daran, dass es sich um einen Methodenaufruf handelt, dem ich vollkommen zustimme. Ich bevorzuge meine Programmiersprachen expliziter als impliziter.
Corey Ogburn
20
Scala ist eigentlich etwas subtiler: Während andere Sprachen nur eine Parameterliste mit null oder mehr Parametern zulassen, lässt Scala null oder mehr Parameterlisten mit null oder mehr Parametern zu. Ist def foo()also eine Methode mit einer leeren Parameterliste und def fooist eine Methode ohne Parameterliste und die beiden sind verschiedene Dinge ! Im Allgemeinen muss eine Methode ohne Parameterliste ohne Argumentliste und eine Methode mit leerer Parameterliste mit leerer Argumentliste aufgerufen werden. Das Aufrufen einer Methode ohne Parameterliste mit einem leeren Argument ist tatsächlich…
Jörg W Mittag
19
… Als Aufruf der applyMethode des vom Methodenaufruf zurückgegebenen Objekts mit einer leeren Argumentliste interpretiert werden , während der Aufruf einer Methode mit einer leeren Parameterliste ohne eine Argumentliste je nach Kontext unterschiedlich als Aufruf der Methode mit einer leeren Argumentliste interpretiert werden kann, η-Erweiterung zu einer teilweise angewendeten Methode (dh "Methodenreferenz"), oder es wird überhaupt nicht kompiliert. Außerdem folgt Scala dem Prinzip des einheitlichen Zugriffs, indem nicht (syntaktisch) unterschieden wird, ob eine Methode ohne Argumentliste aufgerufen wird oder auf ein Feld verwiesen wird.
Jörg W Mittag
3
Auch wenn ich Ruby für das Fehlen der erforderlichen "Zeremonie" - Parens, Klammern, explizite Art - schätze, kann ich feststellen, dass Methodenklammern schneller gelesen werden . aber einmal vertraut idiomatisch Ruby ist gut lesbar und auf jeden Fall schneller zu schreiben.
Radarbob
19

Dies ist eigentlich ein ziemlich subtiler Zufall bei der Syntaxauswahl. Ich spreche mit funktionalen Sprachen, die auf der typisierten Lambda-Rechnung basieren.

In diesen Sprachen hat jede Funktion genau ein Argument . Was wir oft als "Mehrfachargumente" betrachten, ist eigentlich ein einziger Parameter des Produkttyps. So zum Beispiel die Funktion, die zwei ganze Zahlen vergleicht:

leq : int * int -> bool
leq (a, b) = a <= b

nimmt ein einzelnes Paar von ganzen Zahlen. Die Klammern bezeichnen nicht die Funktionsparameter. Sie werden verwendet, um das Argument mit einem Muster abzugleichen. Um Sie davon zu überzeugen, dass dies wirklich ein Argument ist, können wir statt der Mustererkennung die projektiven Funktionen verwenden, um das Paar zu dekonstruieren:

leq some_pair = some_pair.1 <= some_pair.2

Somit sind die Klammern wirklich eine Annehmlichkeit, die es uns ermöglicht, Musterabgleiche vorzunehmen und einige Eingaben zu sparen. Sie sind nicht erforderlich.

Was ist mit einer Funktion, die angeblich keine Argumente hat? Eine solche Funktion hat eigentlich Domain Unit. Das einzelne Mitglied von Unitwird normalerweise als geschrieben (), daher werden die Klammern angezeigt.

say_hi : Unit -> string
say_hi a = "Hi buddy!"

Um diese Funktion zu nennen, würden wir es auf einen Wert von Typ gelten müssen Unit, die sein müssen (), so dass wir am Ende schreiben say_hi ().

Es gibt also wirklich keine Argumentliste!

Gartenkopf
quelle
3
Und deshalb ()ähneln leere Klammern Löffeln ohne den Griff.
Mindwin
3
Das Definieren einer leqFunktion, die <eher verwendet als <=verwirrend ist!
KRyan
5
Diese Antwort ist möglicherweise einfacher zu verstehen, wenn zusätzlich zu Ausdrücken wie "Produkttyp" das Wort "Tupel" verwendet wird.
Kevin
Die meisten Formalismen behandeln int f (int x, int y) als eine Funktion von I ^ 2 bis I. Die Lambda-Rechnung ist anders, sie verwendet diese Methode nicht, um mit der hohen Arität anderer Formalismen übereinzustimmen. Stattdessen verwendet die Lambda-Rechnung das Curry. Der Vergleich wäre x -> (y -> (x <= y)). (x -> (y -> (x <= y)) 2 würde zu (y-> 2 <= y) und (x -> (y -> (x <= y)) 2 3 würde zu ( y-> 2 <= y) 3, was wiederum 2 <= 3.
Taemyr
1
@PeriataBreatta Ich bin nicht mit der Geschichte der Verwendung ()zur Darstellung der Einheit vertraut . Es würde mich nicht wundern, wenn zumindest ein Teil des Grundes darin bestünde, einer "parameterlosen Funktion" zu ähneln. Es gibt jedoch eine andere mögliche Erklärung für die Syntax. In der Algebra der Typen Unitist in der Tat die Identität für Produkttypen. Ein binäres Produkt wird geschrieben als (a,b), wir könnten ein unäres Produkt schreiben als (a), also wäre eine logische Erweiterung, das nulläre Produkt als einfach zu schreiben ().
Gardenhead
14

Wenn Sie in Javascript beispielsweise einen Methodennamen ohne () verwenden, wird die Funktion selbst zurückgegeben, ohne sie auszuführen. Auf diese Weise können Sie beispielsweise die Funktion als Argument an eine andere Methode übergeben.

In Java wurde die Wahl getroffen, dass ein Bezeichner gefolgt von () oder (...) einen Methodenaufruf bedeutet, während ein Bezeichner ohne () auf eine Mitgliedsvariable verweist. Dies kann die Lesbarkeit verbessern, da Sie keinen Zweifel haben, ob Sie es mit einer Methode oder einer Membervariablen zu tun haben. Tatsächlich kann derselbe Name sowohl für eine Methode als auch für eine Membervariable verwendet werden. Beide sind mit ihrer jeweiligen Syntax zugänglich.

Florian F
quelle
4
+1 Solange der Code von Menschen gelesen werden muss, ist die Lesbarkeit entscheidend.
Tulains Córdova
1
@ TulainsCórdova Ich bin mir nicht sicher, ob Perl dir zustimmt.
Racheet
@Racheet: Perl kann nicht aber Perl Best Practicestut
slebetman
1
@Racheet Perl ist sehr gut lesbar, wenn es so geschrieben ist, dass es lesbar ist. Das ist für so ziemlich jede nicht-esoterische Sprache gleich. Kurz gesagt, prägnantes Perl ist für Perl-Programmierer sehr gut lesbar, genauso wie Scala- oder Haskell-Code für Leute mit Erfahrung in diesen Sprachen lesbar ist. Ich kann Sie auch auf eine sehr komplizierte Weise sprechen, die grammatikalisch völlig korrekt ist, aber Sie werden trotzdem kein Wort verstehen, auch wenn Sie Deutsch beherrschen. Auch daran ist kein Deutscher schuld. ;)
simbabque
In Java "identifiziert" es keine Methode. Es nennt es. Object::toStringidentifiziert eine Methode.
njzk2
10

Die Syntax folgt der Semantik. Beginnen wir also mit der Semantik:

Wie kann eine Funktion oder Methode verwendet werden?

Tatsächlich gibt es mehrere Möglichkeiten:

  • Eine Funktion kann mit oder ohne Argumente aufgerufen werden
  • Eine Funktion kann als Wert behandelt werden
  • Eine Funktion kann teilweise angewendet werden (Erstellen eines Abschlusses mit mindestens einem Argument weniger und Schließen über die übergebenen Argumente)

Eine ähnliche Syntax für verschiedene Verwendungszwecke ist der beste Weg, um entweder eine mehrdeutige oder zumindest eine verwirrende Sprache zu erstellen (und wir haben genug davon).

In C und C-ähnlichen Sprachen:

  • Das Aufrufen einer Funktion erfolgt mithilfe von Klammern, um die Argumente einzuschließen (möglicherweise gibt es keine), wie in func()
  • Das Behandeln einer Funktion als Wert kann durch einfaches Verwenden ihres Namens wie in &func(C Erstellen eines Funktionszeigers) erfolgen.
  • In einigen Sprachen gibt es kurze Syntaxen für die teilweise Anwendung. Java erlaubt someVariable::someMethodzum Beispiel (beschränkt auf die Methode Empfänger, aber immer noch nützlich)

Beachten Sie, dass jede Verwendung eine andere Syntax aufweist, sodass Sie sie leicht voneinander unterscheiden können.

Matthieu M.
quelle
2
Nun, "leicht" ist eine dieser "es ist nur klar, wenn es bereits verstanden" Situationen. Ein Anfänger wäre völlig berechtigt zu bemerken, dass es ohne Erklärung keineswegs offensichtlich ist, warum eine Interpunktion die Bedeutung hat, die sie hat.
Eric Lippert
2
@ EricLippert: In der Tat bedeutet leicht nicht unbedingt intuitiv. In diesem Fall würde ich jedoch darauf hinweisen, dass Klammern auch für die Funktion "Aufruf" in der Mathematik verwendet werden. Davon abgesehen habe ich Anfänger in vielen Sprachen gefunden, die mehr mit Konzepten / Semantik als mit Syntax im Allgemeinen zu kämpfen hatten.
Matthieu M.
Diese Antwort verwechselt die Unterscheidung zwischen "Currying" und "Teilanwendung". Der ::Operator in Java ist ein Teilanwendungsoperator, kein laufender Operator. Teilanwendung ist eine Operation, die eine Funktion und einen oder mehrere Werte übernimmt und eine Funktion zurückgibt, die weniger Werte akzeptiert. Currying funktioniert nur mit Funktionen, nicht mit Werten.
Periata Breatta
@PeriataBreatta: Guter Punkt, behoben.
Matthieu M.
8

In einer Sprache mit Nebenwirkungen ist es IMO sehr hilfreich, zwischen dem (theoretisch nebenwirkungsfreien) Lesen einer Variablen zu unterscheiden

variable

und Aufrufen einer Funktion, die Nebenwirkungen verursachen kann

launch_nukes()

OTOH, wenn es keine Nebenwirkungen gibt (außer denen, die im Typsystem kodiert sind), ist es sinnlos, zwischen dem Lesen einer Variablen und dem Aufrufen einer Funktion ohne Argumente zu unterscheiden.

Daniel Jour
quelle
1
Beachten Sie, dass das OP den Fall präsentierte, in dem ein Objekt das einzige Argument ist und vor dem Funktionsnamen (der auch einen Bereich für den Funktionsnamen bereitstellt) präsentiert wird. noun.verbDie Syntax könnte eine so klare Bedeutung haben wie noun.verb()und etwas weniger überladen sein. In ähnlicher Weise eine Funktion member_könnte bietet eine freundliche Syntax als eine typische Set - Funktion eine linke Referenz Rückkehr: obj.member_ = new_valuegegen obj.set_member(new_value)(die _Postfix ist ein Hinweis sagen „ausgesetzt“ erinnert an _ Präfixen für „versteckte“ Funktionen).
Paul A. Clayton
1
@ PaulA.Clayton Ich verstehe nicht, wie dies von meiner Antwort nicht abgedeckt wird noun.verb.
Daniel Jour
1
In Bezug auf das Lesen von Variablen, die theoretisch frei von Nebenwirkungen sind, möchte ich nur Folgendes sagen: Achten Sie immer auf falsche Freunde .
Justin Time
8

Keine der anderen Antworten hat versucht, die Frage zu beantworten: Wie viel Redundanz sollte das Design einer Sprache haben? Denn selbst wenn Sie eine Sprache entwerfen können, bei der x = sqrt yx auf die Quadratwurzel von y gesetzt wird, bedeutet dies nicht, dass Sie dies unbedingt tun sollten.

In einer Sprache ohne Redundanz bedeutet jede Zeichenfolge etwas, was bedeutet, dass bei einem einzelnen Fehler keine Fehlermeldung angezeigt wird und Ihr Programm das Falsche tut, was möglicherweise etwas ganz anderes ist als das, was Sie tun beabsichtigt und sehr schwer zu debuggen. (Jeder, der mit regulären Ausdrücken gearbeitet hat, wird es wissen.) Ein wenig Redundanz ist eine gute Sache, da so viele Ihrer Fehler erkannt werden können. Je mehr Redundanz vorhanden ist, desto wahrscheinlicher ist es, dass die Diagnose korrekt ist. Jetzt können Sie das natürlich zu weit bringen (keiner von uns möchte heutzutage in COBOL schreiben), aber es gibt eine Balance, die richtig ist.

Redundanz hilft auch bei der Lesbarkeit, da es mehr Hinweise gibt. Englisch ohne Redundanz wäre sehr schwer zu lesen, und das Gleiche gilt für Programmiersprachen.

Michael Kay
quelle
In gewissem Sinne stimme ich zu: Programmiersprachen sollten einen "Sicherheitsnetz" -Mechanismus haben. Ich bin jedoch anderer Meinung, dass "ein wenig Redundanz" in der Syntax ein guter Weg ist, dies zu tun - das ist Boilerplate . Meistens führt es Rauschen ein, das die Erkennung von Fehlern erschwert und Möglichkeiten für Syntaxfehler eröffnet, die von der Syntax ablenken echte Bugs. Die Art der Ausführlichkeit, die die Lesbarkeit fördert, hat wenig mit Syntax zu tun, sondern eher mit Namenskonventionen . Und ein Sicherheitsnetz wird bei weitem am effizientesten als starkes Typensystem implementiert , bei dem die meisten zufälligen Änderungen das Programm falsch tippen.
Leftaroundabout
@leftaroundabout: Ich denke gerne über Entscheidungen zum Sprachdesign in Bezug auf die Hamming-Distanz nach. Wenn zwei Konstrukte unterschiedliche Bedeutungen haben, ist es oft hilfreich, sie auf mindestens zwei Arten zu unterscheiden und eine Form zu haben, die sich nur auf eine Art unterscheidet, was zu einem Quietschen beim Compiler führt. Die Sprache Designer können in der Regel nicht dafür verantwortlich , dass Identifikatoren leicht unterscheidbar ist, aber wenn zB Zuordnung erfordert :=und Vergleich ==mit =nur verwendbar ist für Kompilierung-Initialisierung, dann ist die Wahrscheinlichkeit , dass Fehler Vergleiche in Zuweisungen drehen würde stark vermindert.
Supercat
@leftaroundabout: Wenn ich eine Sprache entwerfe, würde ich wahrscheinlich den ()Aufruf einer Nullargumentfunktion erfordern, aber auch ein Token, um die Adresse einer Funktion zu übernehmen. Die erstere Aktion ist viel häufiger, daher ist das Speichern eines Tokens im selteneren Fall nicht allzu nützlich.
Supercat
@supercat: naja, wieder denke ich, das löst das problem auf der falschen ebene. Zuweisung und Vergleich sind konzeptionell unterschiedliche Vorgänge, ebenso wie Funktionsbewertung und Funktionsadressierung. Daher sollten sie klar unterscheidbare Typen haben, dann ist es nicht mehr wichtig, ob die Operatoren eine geringe Hamming-Distanz haben, da jeder Tippfehler ein Tippfehler zur Kompilierungszeit ist.
leftaroundabout
@leftaroundabout: Wenn eine Zuweisung zu einer Variablen vom Typ Boolean vorgenommen wird oder wenn der Rückgabetyp einer Funktion selbst ein Zeiger auf eine Funktion ist (oder - in einigen Sprachen - eine tatsächliche Funktion), wird ein Vergleich durch eine Zuweisung ersetzt. oder ein Funktionsaufruf mit einer Funktionsreferenz kann ein Programm ergeben, das syntaktisch gültig ist, aber eine völlig andere Bedeutung als die ursprüngliche Version hat. Bei der Typprüfung werden im Allgemeinen einige solche Fehler festgestellt, aber die Syntax zu unterscheiden scheint ein besserer Ansatz zu sein.
Supercat
5

In den meisten Fällen handelt es sich hierbei um syntaktische Auswahlmöglichkeiten der Grammatik der Sprache. Für die Grammatik ist es nützlich, wenn die verschiedenen Einzelkonstrukte (relativ) eindeutig sind, wenn sie alle zusammen genommen werden. (Wenn es Unklarheiten gibt, wie in einigen C ++ - Deklarationen, müssen bestimmte Regeln für die Auflösung festgelegt werden.) Der Compiler kann nicht raten. Es ist erforderlich, die Sprachspezifikation zu befolgen.


Visual Basic unterscheidet in verschiedenen Formen zwischen Prozeduren, die keinen Wert zurückgeben, und Funktionen.

Prozeduren müssen als Anweisung aufgerufen werden und erfordern keine Parens, sondern nur durch Kommas getrennte Argumente, falls vorhanden. Funktionen müssen als Teil eines Ausdrucks aufgerufen werden und erfordern die Parens.

Es ist eine relativ unnötige Unterscheidung, die das manuelle Umgestalten zwischen den beiden Formen schmerzhafter macht, als es sein muss.

(Auf der anderen Seite Visual Basic verwendet die gleichen Pars ()für Array - Referenzen wie für Funktionsaufrufe, so Referenzen Array aussehen Funktion aufruft. Und das erleichtert manuelles Refactoring eines Arrays in einen Funktionsaufruf! So konnten wir darüber nachdenken andere Sprachen verwenden []‚s für Array-Referenzen, aber ich schweife ab ...)


In den Sprachen C und C ++ wird auf den Inhalt von Variablen automatisch unter Verwendung ihres Namens zugegriffen. Wenn Sie auf die Variable selbst anstelle ihres Inhalts verweisen möchten, wenden Sie den unären &Operator an.

Diese Art von Mechanismus könnte auch auf Funktionsnamen angewendet werden. Der rohe Funktionsname könnte einen Funktionsaufruf implizieren, während ein unärer &Operator verwendet wird, um die Funktion (selbst) als Daten zu bezeichnen. Persönlich mag ich die Idee, auf nebenwirkungsfreie Funktionen ohne Argumente mit der gleichen Syntax wie Variablen zuzugreifen.

Dies ist durchaus plausibel (wie auch andere syntaktische Möglichkeiten hierfür).

Erik Eidt
quelle
2
Das Fehlen von Klammern bei Prozeduraufrufen in Visual Basic ist zwar eine unnötige Unterscheidung im Vergleich zu den Funktionsaufrufen, das Hinzufügen erforderlicher Klammern wäre jedoch eine unnötige Unterscheidung zwischen Anweisungen , von denen viele in einer von BASIC abgeleiteten Sprache erhebliche Auswirkungen haben erinnert an Verfahren. Es ist auch eine Weile her, dass ich mit einer MS BASIC-Version gearbeitet habe, aber lässt sie die Syntax immer noch nicht zu call <procedure name> (<arguments>)? Ich bin mir ziemlich sicher, dass dies eine gültige Methode war, um Prozeduren zu verwenden, als ich in einem anderen Leben QuickBASIC-Programmierung durchgeführt habe ...
Periata Breatta
1
@PeriataBreatta: VB lässt die "call" -Syntax immer noch zu und erfordert sie manchmal in Fällen, in denen das aufzurufende Objekt ein Ausdruck ist [z. B. kann man sagen "Call If (someCondition, Delegate1, Delegate2) (Arguments)", aber direct Aufruf des Ergebnisses eines "If" -Operators als eigenständige Anweisung nicht gültig.
Supercat
@PeriataBreatta Ich mochte VB6s Prozeduraufrufe ohne Klammern, weil sie DSL-ähnlichen Code gut aussehen ließen.
Mark Hurd
@supercat In LinqPad ist es erstaunlich, wie oft ich Calleine Methode verwenden muss, die ich in "normalem" Produktionscode fast nie verwenden muss.
Mark Hurd
5

Konsistenz und Lesbarkeit.

Wenn ich erfahren , dass Sie die Funktion aufrufen Xwie folgt aus : X(arg1, arg2, ...), dann erwarte ich , dass es die gleiche Art und Weise für keine Argumente arbeiten: X().

Gleichzeitig lerne ich, dass Sie die Variable als ein Symbol definieren und wie folgt verwenden können:

a = 5
b = a
...

Was würde ich jetzt denken, wenn ich das finde?

c = X

Deine Vermutung ist genauso gut wie meine. Unter normalen Umständen ist das Xeine Variable. Aber wenn wir Ihren Weg gehen würden, könnte es auch eine Funktion sein! Muss ich mir merken, ob welches Symbol welcher Gruppe (Variablen / Funktionen) zugeordnet ist?

Wir könnten künstliche Einschränkungen auferlegen, z. B. "Funktionen beginnen mit Großbuchstaben. Variablen beginnen mit Kleinbuchstaben". Dies ist jedoch nicht erforderlich und kompliziert die Dinge, obwohl es manchmal hilfreich sein kann, einige Entwurfsziele zu erreichen.

Randnotiz 1: Bei anderen Antworten wird eines völlig ignoriert: Mit der Sprache können Sie möglicherweise denselben Namen für Funktion und Variable verwenden und nach Kontext unterscheiden. Siehe Common Lispals Beispiel. Funktion xund Variable xkoexistieren perfekt.

Side-Anmerkung 2: Die akzeptierte Antwort zeigt uns die Syntax: object.functionname. Erstens ist es nicht universell für Sprachen mit erstklassigen Funktionen. Zweitens würde ich als Programmierer dies als zusätzliche Information behandeln: functionnamegehört zu einem object. Ob objectes sich um ein Objekt, eine Klasse oder einen Namespace handelt, spielt keine Rolle, aber es sagt mir, dass es irgendwo hingehört . Dies bedeutet, dass Sie entweder object.für jede globale Funktion eine künstliche Syntax hinzufügen oder eine objectfür alle globalen Funktionen erstellen müssen.

In beiden Fällen verlieren Sie die Möglichkeit, separate Namespaces für Funktionen und Variablen zu haben.

MatthewRock
quelle
Jep. Konsistenz ist ein guter Grund, Punkt zu setzen (nicht, dass Ihre anderen Punkte nicht gültig sind - sie sind es).
Matthew Read
4

Nehmen Sie zum Hinzufügen zu den anderen Antworten das folgende C-Beispiel:

void *func_factory(void)
{
        return 0;
}

void *(*ff)(void);

void example()
{
        ff = func_factory;
        ff = func_factory();
}

Wenn der Aufrufoperator optional wäre, gäbe es keine Möglichkeit, zwischen der Funktionszuweisung und dem Funktionsaufruf zu unterscheiden.

Dies ist noch problematischer in Sprachen ohne Typensystem, z. B. JavaScript, in denen die Typinferenz nicht verwendet werden kann, um herauszufinden, was eine Funktion ist und was nicht.

Mark K. Cowan
quelle
3
JavaScript fehlt kein Typensystem. Es fehlen Typdeklarationen . Aber es ist sich des Unterschieds zwischen verschiedenen Werttypen durchaus bewusst.
Periata Breatta
1
Periata Breatta: Der Punkt ist, dass Java-Variablen und -Eigenschaften möglicherweise Typen von beliebigen Werten enthalten, sodass Sie nicht immer wissen können, ob es sich beim Lesen des Codes um eine Funktion handelt oder nicht.
Reinierpost
Was macht diese Funktion zum Beispiel? function cross(a, b) { return a + b; }
Mark K Cowan
@ MarkKCowan Daraus folgt: ecma-international.org/ecma-262/6.0/…
curiousdannii