Warum haben die meisten Programmiersprachen ein spezielles Schlüsselwort oder eine spezielle Syntax zum Deklarieren von Funktionen? [geschlossen]


39

Die meisten Programmiersprachen (sowohl dynamisch als auch statisch typisierte Sprachen) haben spezielle Schlüsselwörter und / oder Syntax, die sich stark von der Deklaration von Variablen zum Deklarieren von Funktionen unterscheiden. Ich sehe Funktionen so, als würde man eine andere benannte Entität deklarieren:

Zum Beispiel in Python:

x = 2
y = addOne(x)
def addOne(number): 
  return number + 1

Warum nicht:

x = 2
y = addOne(x)
addOne = (number) => 
  return number + 1

Ebenso in einer Sprache wie Java:

int x = 2;
int y = addOne(x);

int addOne(int x) {
  return x + 1;
}

Warum nicht:

int x = 2;
int y = addOne(x);
(int => int) addOne = (x) => {
  return x + 1;
}

Diese Syntax scheint natürlicher zu sein, um etwas zu deklarieren (sei es eine Funktion oder eine Variable) und ein Schlüsselwort weniger wie defoder functionin einigen Sprachen. Und, IMO, es ist konsistenter (ich schaue an der gleichen Stelle, um den Typ einer Variablen oder Funktion zu verstehen) und macht es wahrscheinlich ein bisschen einfacher, die Parser / Grammatik zu schreiben.

Ich weiß, dass nur sehr wenige Sprachen diese Idee verwenden (CoffeeScript, Haskell), aber die meisten gängigen Sprachen haben eine spezielle Syntax für Funktionen (Java, C ++, Python, JavaScript, C #, PHP, Ruby).

Sogar in Scala, das beide Möglichkeiten unterstützt (und Typinferenz hat), ist es üblicher zu schreiben:

def addOne(x: Int) = x + 1

Eher, als:

val addOne = (x: Int) => x + 1

IMO, zumindest in Scala, dies ist wahrscheinlich die am leichtesten verständliche Version, aber diese Redewendung wird selten befolgt:

val x: Int = 1
val y: Int = addOne(x)
val addOne: (Int => Int) = x => x + 1

Ich arbeite an meiner eigenen Spielzeugsprache und frage mich, ob es irgendwelche Fallstricke gibt, wenn ich meine Sprache so gestalte und wenn es historische oder technische Gründe gibt, denen dieses Muster nicht weitestgehend folgt.


9
Der Grund ist wahrscheinlich historisch. Wer es zuerst getan hat, hat es so gemacht, und alle anderen haben es kopiert. Aber ich bezweifle, dass wir es sicher wissen können.

29
Ich denke, es liegt daran, dass eine Funktion oder Methode einfach nicht nur eine andere benannte Entität ist . In funktionalen Sprachen sind sie (Sie können sie weitergeben usw.), aber in anderen Sprachen (wie Java) ist eine Funktion oder Methode etwas ganz anderes als eine einfache Variable und kann nicht als solche behandelt werden (ich gebe zu, Java 8 schwächt dies irgendwie ab) Anweisung), daher ist es sinnvoll, Funktionen / Methoden unterschiedlich zu definieren, da sie sich unterschiedlich verhalten .
26.09.14, 11684 Uhr,

15
Ich bin nicht einverstanden, dass Ihr Vorschlag eine natürlichere Art ist, Funktionen zu deklarieren. Ich mag die Syntax von Coffeescript wirklich nicht und ein Teil davon hat mit der Syntax der Funktionsdeklaration zu tun. Tatsache ist, wenn es nicht kaputt ist, reparieren Sie es nicht. 'Def' oder 'function' zu schreiben ist keine große Sache und ist viel offensichtlicher als einige schicke Perl-ähnliche Symbole, die mit gedankenlosen oder müden Augen leicht mit etwas anderem verwechselt werden können. Ganz persönlich finde ich auch, dass Ihre vorgeschlagene Syntax in den Beispielen viel hässlicher aussieht als die aktuelle Syntax.
Roy

22
Wie ist '=>' kein "spezielles Schlüsselwort oder eine spezielle Syntax zum Deklarieren einer Funktion"?
TML

11
Ich weiß nichts über dich, aber für mich (int => int) addOne = (x) => {ist es viel "spezieller" und "komplexer" als int addOne(int) {...
Bogdan Alexandru

Antworten:


48

Ich denke, der Grund dafür ist, dass die meisten populären Sprachen entweder aus der C-Sprachfamilie stammen oder von dieser beeinflusst wurden, im Gegensatz zu funktionalen Sprachen und ihrer Wurzel, der Lambda-Rechnung.

Und in diesen Sprachen sind Funktionen nicht nur ein weiterer Wert:

  • In C ++, C # und Java können Sie Funktionen überladen: Sie können zwei Funktionen mit demselben Namen, aber unterschiedlicher Signatur haben.
  • In C, C ++, C # und Java können Sie Werte haben, die Funktionen darstellen, aber Funktionszeiger, Funktoren, Delegaten und Funktionsschnittstellen unterscheiden sich alle von Funktionen. Ein Grund dafür ist, dass die meisten davon nicht nur Funktionen sind, sondern eine Funktion zusammen mit einem (veränderlichen) Zustand.
  • Variablen sind wandelbar standardmäßig (Sie verwenden müssen const, readonlyoder finalMutation verbieten), aber Funktionen nicht neu zugeordnet werden können.
  • Aus technischer Sicht sind Code (der aus Funktionen besteht) und Daten getrennt. Sie belegen normalerweise verschiedene Teile des Speichers und auf sie wird unterschiedlich zugegriffen: Code wird einmal geladen und dann nur ausgeführt (aber nicht gelesen oder geschrieben), wohingegen Daten häufig ständig zugewiesen und freigegeben werden und geschrieben und gelesen, aber nie ausgeführt werden.

    Und da C "nah am Metall" sein sollte, ist es sinnvoll, diese Unterscheidung auch in der Syntax der Sprache widerzuspiegeln.

  • Der Ansatz "Funktion ist nur ein Wert", der der funktionalen Programmierung zugrunde liegt, hat in den gängigen Sprachen erst seit relativ kurzer Zeit an Bedeutung gewonnen, wie die späte Einführung von Lambdas in C ++, C # und Java (2011, 2007, 2014) zeigt.


12
C # hatte seit 2005 anonyme Funktionen - das sind nur Lambdas ohne Typinferenz und eine klobige Syntax.
Eric Lippert

2
"Aus technischer Sicht sind Code (der sich aus Funktionen zusammensetzt) ​​und Daten getrennt" - einige Sprachen, wohlwissend, machen LISPs diese Trennung nicht und behandeln Code und Daten nicht wirklich zu unterschiedlich. (LISPs sind das bekannteste Beispiel, aber es gibt eine Reihe anderer Sprachen wie REBOL, die dies tun)
Benjamin Gruenbaum

1
@BenjaminGruenbaum Ich sprach über kompilierten Code im Speicher, nicht über das Sprachniveau.
Svick

C hat Funktionszeiger, die zumindest geringfügig als nur ein anderer Wert angesehen werden können. Ein gefährlicher Wert, mit dem man sich anlegen muss, aber seltsame Dinge sind passiert.
Patrick Hughes

@PatrickHughes Ja, ich erwähne sie in meinem zweiten Punkt. Funktionszeiger unterscheiden sich jedoch erheblich von Funktionen.
Svick

59

Es ist wichtig, dass der Mensch erkennt, dass Funktionen nicht nur "eine andere benannte Entität" sind. Manchmal ist es sinnvoll, sie als solche zu manipulieren, aber sie sind immer noch auf einen Blick zu erkennen.

Es spielt keine Rolle, was der Computer über die Syntax denkt, da ein unverständlicher Klumpen von Zeichen für eine Maschine in Ordnung ist, um sie zu interpretieren, aber für den Menschen wäre es nahezu unmöglich, dies zu verstehen und aufrechtzuerhalten.

Es ist wirklich derselbe Grund, warum wir while- und for-Schleifen, switch- und if-else-Anweisungen usw. haben, obwohl alle letztendlich auf einen Vergleichs- und Sprungbefehl hinauslaufen. Der Grund ist, dass es zum Wohle der Menschen da ist, die den Code pflegen und verstehen.

Wenn Sie Ihre Funktionen als "eine andere benannte Entität" haben, wie Sie es vorschlagen, wird es schwieriger, Ihren Code zu sehen und damit zu verstehen.


12
Ich bin nicht einverstanden , dass Funktionen als benannte Entitäten Behandlung notwendig macht Code schwerer zu verstehen. Dies gilt mit ziemlicher Sicherheit für jeden Code, der einem prozeduralen Paradigma folgt, aber möglicherweise nicht für Code, der einem funktionalen Paradigma folgt.
Kyle Strand

29
"Es ist wirklich derselbe Grund, warum wir while- und for-Schleifen, switch- und if-else- Anweisungen usw. haben, obwohl sich letztendlich alle auf einen Vergleichs- und Sprungbefehl beschränken. " +1 dazu. Wenn alle Programmierer mit der Maschinensprache vertraut wären, bräuchten wir nicht all diese ausgefallenen Programmiersprachen (High- oder Low-Level). Aber die Wahrheit ist: Jeder hat ein anderes Komfortniveau, was die Nähe der Maschine anbelangt, an der er seinen Code schreiben möchte. Sobald Sie Ihr Level gefunden haben, müssen Sie sich nur noch an die Syntax halten (oder Ihre eigenen Sprachspezifikationen und den Compiler schreiben, wenn Sie sich wirklich Sorgen machen).
Hoki

12
+1. Eine prägnantere Version könnte lauten: "Warum unterscheiden alle natürlichen Sprachen Substantive von Verben?"
MSW

2
"Ein unverständlicher Klumpen von Zeichen ist in Ordnung für eine Maschine zu interpretieren, aber das wäre für den Menschen nahezu unmöglich zu verstehen und beizubehalten." Man passt sich schnell an die Syntax an. Dieser Vorschlag ist eine viel weniger radikale Abweichung von der Konvention als der OO-Methodenaufruf object.method (args) zu seiner Zeit, wobei sich herausgestellt hat, dass Letzteres für den Menschen nicht so gut wie unmöglich zu verstehen und beizubehalten ist. Trotz der Tatsache, dass in den meisten OO-Sprachen Methoden nicht als in Klasseninstanzen gespeichert betrachtet werden sollten.
Marc van Leeuwen

4
@MarcvanLeeuwen. Ihr Kommentar ist richtig und sinnvoll, aber die Art und Weise, in der der ursprüngliche Beitrag redigiert wird, ist verwirrend. Es gibt eigentlich 2 Fragen: (i) Warum ist es so? und (ii) Warum nicht stattdessen so? . Die Antwort von whatsisnamebetraf eher den ersten Punkt (und warnte vor einer gewissen Gefahr, diese ausfallsicheren Punkte zu entfernen), während sich Ihr Kommentar eher auf den zweiten Teil der Frage bezog. Es ist in der Tat möglich, diese Syntax zu ändern (und wie Sie beschrieben haben, wurde dies bereits oft getan ...), aber sie passt nicht zu jedem (da oop auch nicht zu jedem passt.
Hoki

10

Es könnte Sie interessieren, zu erfahren, dass eine Sprache namens ALGOL 68 schon in prähistorischer Zeit eine Syntax verwendet hat, die der von Ihnen vorgeschlagenen nahe kommt. Wenn Sie erkennen, dass Funktionsbezeichner genau wie andere Bezeichner an Werte gebunden sind, können Sie in dieser Sprache eine Funktion (Konstante) mithilfe der Syntax deklarieren

Funktionstyp name = ( Parameterliste ) Ergebnistyp : body ;

Konkret würde dein Beispiel lauten

PROC (INT)INT add one = (INT n) INT: n+1;

In Anbetracht der Redundanz, dass der ursprüngliche Typ aus dem RHS der Deklaration abgelesen werden kann und ein Funktionstyp immer mit beginnt PROC, könnte (und würde) dies vertraglich geregelt werden

PROC add one = (INT n) INT: n+1;

beachte aber, dass das =noch vor der parameterliste kommt. Beachten Sie auch, dass Sie, wenn Sie eine Funktionsvariable möchten (der später ein anderer Wert desselben Funktionstyps zugewiesen werden könnte), =diese durch :=eine der folgenden ersetzen sollten :

PROC (INT)INT func var := (INT n) INT: n+1;
PROC func var := (INT n) INT: n+1;

In diesem Fall sind jedoch beide Formen tatsächlich Abkürzungen; Da der func varBezeichner einen Verweis auf eine lokal erzeugte Funktion bezeichnet, wäre die vollständig erweiterte Form

REF PROC (INT)INT func var = LOC PROC (INT)INT := (INT n) INT: n+1;

Diese spezielle syntaktische Form ist leicht zu gewöhnen, hatte aber in anderen Programmiersprachen eindeutig keine große Anhängerschaft. Auch funktionale Programmiersprachen wie Haskell bevorzugen den Stil f n = n+1mit = folgenden in der Parameterliste. Ich denke, der Grund ist hauptsächlich psychologischer Natur. Schließlich bevorzugen selbst Mathematiker nicht oft, wie ich es tue, f = nn + 1 gegenüber f ( n ) = n + 1.

Übrigens macht die obige Diskussion einen wichtigen Unterschied zwischen Variablen und Funktionen deutlich: Funktionsdefinitionen binden normalerweise einen Namen an einen bestimmten Funktionswert, der später nicht geändert werden kann, wohingegen Variablendefinitionen normalerweise einen Bezeichner mit einem Anfangswert einführen , aber einen solchen kann sich später ändern. (Dies ist keine absolute Regel. Funktionsvariablen und Nichtfunktionskonstanten kommen in den meisten Sprachen vor.) Außerdem ist der in einer Funktionsdefinition gebundene Wert in kompilierten Sprachen normalerweise eine Konstante zur Kompilierungszeit, sodass Aufrufe der Funktion möglich sind kompiliert mit einer festen Adresse im Code. In C / C ++ ist dies sogar eine Voraussetzung; das Äquivalent des ALGOL 68

PROC (REAL) REAL f = IF mood=sunny THEN sin ELSE cos FI;

kann nicht in C ++ geschrieben werden, ohne einen Funktionszeiger einzuführen. Diese Art von spezifischen Einschränkungen rechtfertigen die Verwendung einer anderen Syntax für Funktionsdefinitionen. Sie hängen jedoch von der Sprachsemantik ab, und die Rechtfertigung gilt nicht für alle Sprachen.


1
"Mathematiker bevorzugen nicht f = nn + 1 gegenüber f ( n ) = n + 1" ... Ganz zu schweigen von Physikern, die nur f = n + 1 schreiben
möchten

1
@leftaroundabout, das ist, weil ⟼ ein Schmerz ist, um zu schreiben. Ehrlich.
Pierre Arlaud

8

Sie haben Java und Scala als Beispiele genannt. Sie haben jedoch eine wichtige Tatsache übersehen: Das sind keine Funktionen, das sind Methoden. Methoden und Funktionen unterscheiden sich grundlegend . Funktionen sind Objekte, Methoden gehören zu Objekten.

In Scala, das sowohl Funktionen als auch Methoden enthält, gibt es die folgenden Unterschiede zwischen Methoden und Funktionen:

  • Methoden können generisch sein, Funktionen nicht
  • Methoden können keine, eine oder mehrere Parameterlisten haben, Funktionen haben immer genau eine Parameterliste
  • Methoden können eine implizite Parameterliste haben, Funktionen nicht
  • Methoden können optionale Parameter mit Standardargumenten haben, Funktionen nicht
  • Methoden können wiederholte Parameter haben, Funktionen nicht
  • Methoden können By-Name-Parameter haben, Funktionen nicht
  • Methoden können mit benannten Argumenten aufgerufen werden, Funktionen nicht
  • Funktionen sind Objekte, Methoden nicht

Ihr vorgeschlagener Ersatz funktioniert also einfach nicht, zumindest in diesen Fällen.


2
"Funktionen sind Objekte, Methoden gehören zu Objekten." Soll das für alle Sprachen gelten (wie C ++)?
Svick

9
"Methoden können By-Name-Parameter haben, Funktionen nicht" - dies ist kein grundlegender Unterschied zwischen Methoden und Funktionen, es ist nur eine Eigenart von Scala. Das Gleiche gilt für die meisten (alle?) Anderen.
user253751

1
Methoden sind grundsätzlich nur eine spezielle Art von Funktionen. -1. Beachten Sie, dass Java (und damit auch Scala) keine anderen Funktionen hat. Es ist "Funktionen" sind Objekte mit einer Methode (im Allgemeinen können Funktionen keine Objekte oder sogar erstklassige Werte sein; ob sie sind, hängt von der Sprache ab).
Jan Hudec

@JanHudec: Java hat semantisch sowohl Funktionen als auch Methoden. es verwendet die Terminologie "statische Methoden", wenn es sich auf Funktionen bezieht, aber eine solche Terminologie löscht nicht die semantische Unterscheidung.
Supercat

3

Die Gründe, die mir einfallen, sind:

  • Für den Compiler ist es einfacher zu wissen, was wir deklarieren.
  • Es ist wichtig, dass wir (auf triviale Weise) wissen, ob es sich um eine Funktion oder eine Variable handelt. Funktionen sind normalerweise Black Boxes und die interne Implementierung ist uns egal. Ich mag keine Typinferenz für Rückgabetypen in Scala, weil ich glaube, dass es einfacher ist, eine Funktion mit einem Rückgabetyp zu verwenden: Es ist oft die einzige Dokumentation, die bereitgestellt wird.
  • Und das wichtigste ist die folgende Crowd- Strategie, die beim Entwerfen von Programmiersprachen verwendet wird. C ++ wurde erstellt, um C-Programmierer zu stehlen, und Java wurde so entwickelt, dass es C ++ - Programmierer nicht erschreckt, und C #, um Java-Programmierer anzulocken. Sogar C #, das meiner Meinung nach eine sehr moderne Sprache mit einem großartigen Team ist, hat einige Fehler aus Java oder sogar aus C kopiert.

2
"... und C #, um Java-Programmierer anzulocken" - das ist fraglich, C # ist C ++ viel ähnlicher als Java. und manchmal sieht es aus wie es mit Java (keine Platzhalter, keine innere Klassen, keine Rückgabetyp Kovarianz ...) absichtlich inkompatibel gemacht wurde
Sarge Borsch

1
@SargeBorsch Ich glaube nicht , wurde es absichtlich mit Java nicht kompatibel gemacht, ich bin mir ziemlich sicher , dass das C # Team einfach versucht , es richtig zu machen, oder es besser machen , aber man es suchen. Microsoft hat bereits eine eigene Java & JVM geschrieben und wurde verklagt. So C # Team war wahrscheinlich sehr motivierte Java zu verdunkeln. Es ist sicherlich unvereinbar und besser dafür. Java begann mit einigen grundlegenden Einschränkungen und ich bin froh, dass sich das C # -Team zum Beispiel dafür entschieden hat, Generika anders zu erstellen (sichtbar im Typensystem und nicht nur bei Zucker).
codenheim

2

Umgekehrt: Wenn Sie nicht daran interessiert sind, den Quellcode auf einem Computer mit extremen RAM-Einschränkungen zu bearbeiten oder die Zeit zum Lesen von einer Diskette zu minimieren, was ist dann falsch an der Verwendung von Schlüsselwörtern?

Sicherlich ist es besser zu lesen x=y+zals store the value of y plus z into x, aber das bedeutet nicht, dass Interpunktionszeichen von Natur aus "besser" sind als Stichwörter. Wenn Variablen i, jund ksind Integerund xsind Real, berücksichtigen Sie die folgenden Zeilen in Pascal:

k := i div j;
x := i/j;

In der ersten Zeile wird eine ganze Zahl abgeschnitten, in der zweiten eine reelle Zahl. Die Unterscheidung kann gut gemacht werden, weil Pascal divals Operator für die Ganzzahltrennung verwendet, anstatt zu versuchen, ein Interpunktionszeichen zu verwenden, das bereits einen anderen Zweck hat (reelle Zahlenteilung).

Während es einige Kontexte gibt, in denen es hilfreich sein kann, eine Funktionsdefinition kurz zu fassen (z. B. ein Lambda, das als Teil eines anderen Ausdrucks verwendet wird), sollen Funktionen im Allgemeinen hervorstechen und als Funktionen leicht visuell erkennbar sein. Obwohl es möglich sein könnte, die Unterscheidung viel subtiler zu gestalten und nur Interpunktionszeichen zu verwenden, worum geht es dann? Das Aussprechen Function Foo(A,B: Integer; C: Real): Stringmacht deutlich, wie die Funktion heißt, welche Parameter sie erwartet und was sie zurückgibt. Vielleicht könnte man es durch Ersetzen Functiondurch Interpunktionszeichen um sechs oder sieben Zeichen verkürzen , aber was würde man gewinnen?

Zu beachten ist auch, dass in den meisten Frameworks ein grundlegender Unterschied besteht zwischen einer Deklaration, die immer einen Namen entweder mit einer bestimmten Methode oder einer bestimmten virtuellen Bindung verknüpft, und einer Deklaration, die eine Variable erstellt, die zunächst eine bestimmte Methode oder Bindung identifiziert, jedoch könnte zur Laufzeit geändert werden , um einen anderen zu identifizieren. Da dies in den meisten prozeduralen Frameworks semantisch sehr unterschiedliche Konzepte sind, ist es sinnvoll, dass sie eine unterschiedliche Syntax haben.


3
Ich denke nicht, dass es bei dieser Frage um Prägnanz geht. Zumal zum Beispiel void f() {}tatsächlich kürzer ist als das Lambda-Äquivalent in C ++ ( auto f = [](){};), C # ( Action f = () => {};) und Java ( Runnable f = () -> {};). Die Prägnanz von Lambdas beruht auf Typinferenz und Auslassen return, aber ich denke nicht, dass dies mit dem zusammenhängt, was diese Frage stellt.
Svick

2

Nun, der Grund könnte sein, dass diese Sprachen sozusagen nicht funktionsfähig genug sind. Mit anderen Worten, Sie definieren Funktionen eher selten. Daher ist die Verwendung eines zusätzlichen Schlüsselworts akzeptabel.

In den Sprachen des ML- oder Miranda-Erbes OTOH definieren Sie die meiste Zeit Funktionen. Sehen Sie sich zum Beispiel einen Haskell-Code an. Es ist buchstäblich meist eine Folge von Funktionsdefinitionen, von denen viele lokale Funktionen und lokale Funktionen dieser lokalen Funktionen haben. Daher wäre ein unterhaltsames Schlüsselwort in Haskell ein Fehler, der ebenso groß ist wie die Anforderung einer Assingment-Anweisung in einer zwingenden Sprache, um mit der Zuweisung zu beginnen . Ursache Zuordnung ist wahrscheinlich die mit Abstand häufigste Aussage.


1

Persönlich sehe ich keinen schwerwiegenden Fehler in Ihrer Idee; Möglicherweise stellen Sie fest, dass es schwieriger ist, bestimmte Dinge mit Ihrer neuen Syntax auszudrücken, als Sie erwartet hatten, und / oder Sie müssen sie möglicherweise überarbeiten (indem Sie verschiedene Sonderfälle und andere Funktionen usw. hinzufügen), aber ich bezweifle, dass Sie sich selbst finden die Idee ganz aufgeben zu müssen.

Die von Ihnen vorgeschlagene Syntax ähnelt mehr oder weniger einer Variante einiger der Notationsstile, die manchmal zum Ausdrücken von Funktionen oder Funktionstypen in der Mathematik verwendet werden. Dies bedeutet, dass es, wie alle Grammatiken, wahrscheinlich einige Programmierer mehr ansprechen wird als andere. (Als Mathematiker mag ich es zufällig.)

Sie sollten jedoch beachten , dass in den meisten Sprachen, die def-Stil Syntax (dh die traditionelle Syntax) nicht verhalten sich anders als ein Standard - Variablenzuweisung.

  • In der Cund C++-Familie werden Funktionen im Allgemeinen nicht als "Objekte" behandelt, dh als Teile von eingegebenen Daten, die kopiert und auf den Stapel gelegt werden sollen und so weiter. (Ja, Sie können Funktionszeiger haben, aber diese zeigen immer noch auf ausführbaren Code, nicht auf "Daten" im typischen Sinne.)
  • In den meisten OO-Sprachen gibt es eine spezielle Behandlung für Methoden (dh Member-Funktionen). Das heißt, sie sind nicht nur Funktionen, die im Rahmen einer Klassendefinition deklariert wurden. Der wichtigste Unterschied besteht darin, dass das Objekt, für das die Methode aufgerufen wird, normalerweise als impliziter erster Parameter an die Methode übergeben wird. Python macht dies explizit mit self(was übrigens eigentlich kein Schlüsselwort ist; Sie können jeden gültigen Bezeichner zum ersten Argument einer Methode machen).

Sie müssen überlegen, ob Ihre neue Syntax genau (und hoffentlich intuitiv) das darstellt, was der Compiler oder Interpreter tatsächlich tut. Es kann hilfreich sein, sich beispielsweise über den Unterschied zwischen Lambdas und Methoden in Ruby zu informieren. Auf diese Weise erhalten Sie eine Vorstellung davon, wie sich Ihr Funktions-Daten-Paradigma vom typischen OO / prozeduralen Paradigma unterscheidet.


1

Für einige Sprachen sind Funktionen keine Werte. In so einer Sprache das zu sagen

val f = << i : int  ... >> ;

ist eine Funktionsdefinition, wohingegen

val a = 1 ;

deklariert eine Konstante, ist verwirrend, weil Sie eine Syntax verwenden, um zwei Dinge zu bedeuten.

Andere Sprachen wie ML, Haskell und Scheme behandeln Funktionen als Werte der ersten Klasse, bieten dem Benutzer jedoch eine spezielle Syntax zum Deklarieren von Konstanten mit Funktionswerten. * Sie wenden die Regel an, dass "Verwendung die Form verkürzt". Dh wenn ein Konstrukt sowohl allgemein als auch ausführlich ist, sollten Sie dem Benutzer eine Kurzform geben. Es ist unelegant, dem Benutzer zwei verschiedene Syntaxen zu geben, die genau dasselbe bedeuten. manchmal sollte Eleganz dem Nutzen geopfert werden.

Wenn Funktionen in Ihrer Sprache erstklassig sind, warum dann nicht versuchen, eine Syntax zu finden, die so präzise ist, dass Sie nicht versucht sind, einen syntaktischen Zucker zu finden?

- Bearbeiten -

Ein weiteres Thema, das (noch) niemand angesprochen hat, ist die Rekursion. Wenn du erlaubst

{ 
    val f = << i : int ... g(i-1) ... >> ;
    val g = << j : int ... f(i-1) ... >> ;
    f(x)
}

und du erlaubst

{
    val a = 42 ;
    val b = a + 1 ;
    a
} ,

Daraus folgt, dass Sie zulassen sollten

{
    val a = b + 1 ; 
    val b = a - 1 ;
    a
} ?

In einer faulen Sprache (wie Haskell) gibt es hier kein Problem. In einer Sprache ohne statische Überprüfungen (wie LISP) gibt es hier keine Probleme. In einer statisch überprüften Sprache müssen Sie jedoch vorsichtig sein, wie die Regeln für die statische Überprüfung definiert sind, wenn Sie die ersten beiden zulassen und die letzte verbieten möchten.

- Ende der Bearbeitung -

* Es könnte argumentiert werden, dass Haskell nicht in diese Liste gehört. Es gibt zwei Möglichkeiten, eine Funktion zu deklarieren, aber beide sind in gewissem Sinne Verallgemeinerungen der Syntax zum Deklarieren von Konstanten anderer Typen


0

Dies kann in dynamischen Sprachen hilfreich sein, in denen der Typ nicht so wichtig ist, in statisch typisierten Sprachen jedoch nicht so gut lesbar ist, wenn Sie immer den Typ Ihrer Variablen kennen möchten. Außerdem ist es in objektorientierten Sprachen ziemlich wichtig, den Typ Ihrer Variablen zu kennen, um zu wissen, welche Operationen unterstützt werden.

In Ihrem Fall wäre eine Funktion mit 4 Variablen:

(int, long, double, String => int) addOne = (x, y, z, s) => {
  return x + 1;
}

Wenn ich mir den Funktionsheader ansehe und sehe (x, y, z, s), kenne ich aber die Typen dieser Variablen nicht. Wenn ich wissen will, zwelcher Typ der dritte Parameter ist, muss ich mir den Anfang der Funktion ansehen und mit der Zählung von 1, 2, 3 beginnen und dann sehen, dass der Typ ist double. Auf die erstere Art schaue ich direkt und sehe double z.


3
Natürlich gibt es dieses wundervolle Ding namens Typinferenz, mit dem Sie so etwas wie var addOne = (int x, long y, double z, String s) => { x + 1 }in einer nicht-schwachsinnigen, statisch typisierten Sprache Ihrer Wahl schreiben können (Beispiele: C #, C ++, Scala). Auch die sonst sehr eingeschränkte lokale Typinferenz von C # ist dafür völlig ausreichend. Diese Antwort kritisiert lediglich eine bestimmte Syntax, die an erster Stelle zweifelhaft ist und nirgendwo verwendet wird (obwohl Haskells Syntax ein sehr ähnliches Problem hat).
Amon

4
@amon Seine Antwort mag die Syntax kritisieren, weist aber auch darauf hin, dass die beabsichtigte Syntax in der Frage möglicherweise nicht für alle "natürlich" ist.

1
@amon Die Nützlichkeit von Typinferenz ist nicht für alle eine wunderbare Sache. Wenn Sie Code häufiger lesen als Code schreiben, ist die Typinferenz schlecht, da Sie beim Lesen des Codes den Typ in Ihrem Kopf ableiten müssen. und es ist viel schneller, den Typ zu lesen, als tatsächlich darüber nachzudenken, welcher Typ verwendet wird.
m3th0dman

1
Die Kritik scheint mir berechtigt zu sein. Für Java scheint das OP zu denken, dass [Eingabe, Ausgabe, Name, Eingabe] eine einfachere / klarere Funktion ist als einfach [Ausgabe, Name, Eingabe]? Wie @ m3th0dman mit längeren Signaturen feststellt, ist der "sauberere" Ansatz des OP sehr umständlich.
Michael

0

Es gibt einen sehr einfachen Grund für eine solche Unterscheidung in den meisten Sprachen: Es muss zwischen Bewertung und Erklärung unterschieden werden . Ihr Beispiel ist gut: Warum nicht Variablen mögen? Nun, Variablenausdrücke werden sofort ausgewertet.

Haskell hat ein spezielles Modell, bei dem es keinen Unterschied zwischen Auswertung und Deklaration gibt, weshalb kein spezielles Schlüsselwort erforderlich ist.


2
Aber eine Syntax für Lambda-Funktionen löst dies auch. Warum also nicht?
Svick

0

Funktionen werden in den meisten Sprachen anders deklariert als Literale, Objekte usw., da sie unterschiedlich verwendet, unterschiedlich getestet und mit unterschiedlichen potenziellen Fehlerquellen behaftet sind.

Wenn einer Funktion eine dynamische Objektreferenz oder ein veränderbares Objekt übergeben wird, kann die Funktion den Wert des Objekts während der Ausführung ändern. Diese Art von Nebeneffekt kann es schwierig machen, die Funktionsweise einer Funktion zu verfolgen, wenn sie in einem komplexen Ausdruck verschachtelt ist. Dies ist ein häufiges Problem in Sprachen wie C ++ und Java.

Erwägen Sie das Debuggen einer Art Kernelmodul in Java, bei dem jedes Objekt eine toString () - Operation hat. Es ist zwar zu erwarten, dass die Methode toString () das Objekt wiederherstellt, das Objekt muss jedoch möglicherweise zerlegt und neu zusammengesetzt werden, um seinen Wert in ein String-Objekt zu übersetzen. Wenn Sie versuchen, die Methoden zu debuggen, die toString () aufruft (in einem Hook-and-Template-Szenario), um seine Arbeit zu erledigen, und versehentlich das Objekt im Variablenfenster der meisten IDEs markieren, kann dies zum Absturz des Debuggers führen. Dies liegt daran, dass die IDE versucht, das Objekt, das genau den Code aufruft, den Sie gerade debuggen, an String () zu übergeben. Kein primitiver Wert macht jemals so einen Mist, weil die semantische Bedeutung primitiver Werte von der Sprache und nicht vom Programmierer bestimmt wird.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.