Go: Vorschlag: Go 2: Lightweight anonyme Funktionssyntax

Erstellt am 17. Aug. 2017  ·  53Kommentare  ·  Quelle: golang/go

Viele Sprachen bieten eine leichtgewichtige Syntax zur Angabe anonymer Funktionen, bei der der Funktionstyp aus dem umgebenden Kontext abgeleitet wird.

Betrachten Sie ein leicht konstruiertes Beispiel aus der Go-Tour (https://tour.golang.org/moretypes/24):

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

var _ = compute(func(a, b float64) float64 { return a + b })

Viele Sprachen erlauben es in diesem Fall, die Parameter- und Rückgabetypen der anonymen Funktion zu eliminieren, da sie möglicherweise aus dem Kontext abgeleitet werden. Beispielsweise:

// Scala
compute((x: Double, y: Double) => x + y)
compute((x, y) => x + y) // Parameter types elided.
compute(_ + _) // Or even shorter.
// Rust
compute(|x: f64, y: f64| -> f64 { x + y })
compute(|x, y| { x + y }) // Parameter and return types elided.

Ich schlage vor, darüber nachzudenken, ein solches Formular zu Go 2 hinzuzufügen. Ich schlage keine spezifische Syntax vor. In Bezug auf die Sprachspezifikation kann dies als eine Art nicht typisiertes Funktionsliteral betrachtet werden, das jeder kompatiblen Variablen des Funktionstyps zuweisbar ist. Literale dieser Form hätten keinen Standardtyp und könnten nicht auf der rechten Seite von := verwendet werden, so wie x := nil ein Fehler ist.

Verwendet 1: Cap'n Proto

Remote-Aufrufe, die Cap'n Proto verwenden, nehmen einen Funktionsparameter an, dem eine Anforderungsnachricht zum Auffüllen übergeben wird. Von https://github.com/capnproto/go-capnproto2/wiki/Getting-Started :

s.Write(ctx, func(p hashes.Hash_write_Params) error {
  err := p.SetData([]byte("Hello, "))
  return err
})

Verwenden der Rust-Syntax (nur als Beispiel):

s.Write(ctx, |p| {
  err := p.SetData([]byte("Hello, "))
  return err
})

Verwendet 2: Fehlergruppe

Das Paket errgroup (http://godoc.org/golang.org/x/sync/errgroup) verwaltet eine Gruppe von Goroutinen:

g.Go(func() error {
  // perform work
  return nil
})

Verwenden der Scala-Syntax:

g.Go(() => {
  // perform work
  return nil
})

(Da die Funktionssignatur in diesem Fall ziemlich klein ist, könnte dies wohl ein Fall sein, in dem die leichte Syntax weniger klar ist.)

Go2 LanguageChange Proposal

Hilfreichster Kommentar

Ich unterstütze den Vorschlag. Es spart Tipparbeit und hilft bei der Lesbarkeit. Mein Anwendungsfall,

// Type definitions and functions implementation.
type intSlice []int
func (is intSlice) Filter(f func(int) bool) intSlice { ... }
func (is intSlice) Map(f func(int) int) intSlice { ... }
func (is intSlice) Reduce(f func(int, int) int) int { ...  }
list := []int{...} 
is := intSlice(list)

ohne leichtgewichtige anonyme Funktionssyntax:

res := is.Map(func(i int)int{return i+1}).Filter(func(i int) bool { return i % 2 == 0 }).
             Reduce(func(a, b int) int { return a + b })

mit leichtgewichtiger anonymer Funktionssyntax:

res := is.Map((i) => i+1).Filter((i)=>i % 2 == 0).Reduce((a,b)=>a+b)

Alle 53 Kommentare

Ich habe Verständnis für die allgemeine Idee, aber ich finde die konkreten Beispiele nicht sehr überzeugend: Die relativ geringe Einsparung in Bezug auf die Syntax scheint die Mühe nicht wert zu sein. Aber vielleicht gibt es bessere Beispiele oder eine überzeugendere Notation.

(Vielleicht mit Ausnahme des Beispiels für den binären Operator, aber ich bin mir nicht sicher, wie häufig dieser Fall in typischem Go-Code vorkommt.)

Bitte nein, klar ist besser als schlau. Ich finde diese Abkürzungssyntaxen
unmöglich stumpf.

Am Freitag, 18. August 2017, 04:43 Uhr Robert Griesemer [email protected]
schrieb:

Ich bin mit der allgemeinen Idee einverstanden, aber ich finde die konkreten Beispiele
Dazu wenig überzeugend: Die relativ geringen Einsparungen in Sachen Syntax
scheint die Mühe nicht wert zu sein. Aber vielleicht gibt es bessere Beispiele bzw
überzeugendere Notation.


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/golang/go/issues/21498#issuecomment-323159706 oder stumm
der Faden
https://github.com/notifications/unsubscribe-auth/AAAcAxlgwt-iPryyY-d5w8GJho0bY9bkks5sZInfgaJpZM4O6pBB
.

Ich denke, dies ist überzeugender, wenn wir seine Verwendung auf Fälle beschränken, in denen der Funktionskörper ein einfacher Ausdruck ist. Wenn wir einen Block und ein explizites return schreiben müssen, gehen die Vorteile etwas verloren.

Ihre Beispiele werden dann

s.Write(ctx, p => p.SetData([]byte("Hello, "))

g.Go(=> nil)

Die Syntax ist so etwas wie

[ Identifier ] | "(" IdentifierList ")" "=>" ExpressionList

Dies darf nur in einer Zuweisung zu einem Wert vom Typ Funktion verwendet werden (einschließlich Zuweisung zu einem Parameter im Verlauf eines Funktionsaufrufs). Die Anzahl der Bezeichner muss mit der Anzahl der Parameter des Funktionstyps übereinstimmen, und der Funktionstyp bestimmt die Bezeichnertypen. Der Funktionstyp muss null Ergebnisse haben oder die Anzahl der Ergebnisparameter muss mit der Anzahl der Ausdrücke in der Liste übereinstimmen. Der Typ jedes Ausdrucks muss dem Typ des entsprechenden Ergebnisparameters zuordenbar sein. Dies ist auf offensichtliche Weise äquivalent zu einem Funktionsliteral.

Hier liegt wahrscheinlich eine Parsing-Mehrdeutigkeit vor. Es wäre auch interessant, die Syntax zu betrachten

λ [Identifier] | "(" IdentifierList ")" "." ExpressionList

wie in

s.Write(ctx, λp.p.SetData([]byte("Hello, "))

Ein paar weitere Fälle, in denen Verschlüsse häufig verwendet werden.

(Ich versuche derzeit hauptsächlich Anwendungsfälle zu sammeln, um Beweise für/gegen die Nützlichkeit dieser Funktion zu liefern.)

Mir gefällt eigentlich, dass Go nicht länger anonyme Funktionen diskriminiert, wie es Java tut.

In Java ist eine kurze anonyme Funktion, ein Lambda, nett und kurz, während eine längere im Vergleich zu einer kurzen wortreich und hässlich ist. Ich habe sogar irgendwo einen Vortrag/Post gesehen (ich kann ihn jetzt nicht finden), der dazu ermutigt, nur einzeilige Lambdas in Java zu verwenden, weil diese all diese Vorteile der Nicht-Ausführlichkeit haben.

In Go haben wir dieses Problem nicht, sowohl kurze als auch längere anonyme Funktionen sind relativ (aber nicht zu viel) ausführlich, so dass es kein mentales Hindernis gibt, auch längere zu verwenden, was manchmal sehr nützlich ist.

Die Kurzschrift ist in funktionalen Sprachen natürlich, weil alles ein Ausdruck ist und das Ergebnis einer Funktion der letzte Ausdruck in der Definition der Funktion ist.

Eine Kurzschrift zu haben ist schön, also haben andere Sprachen, in denen das obige nicht gilt, sie übernommen.

Aber meiner Erfahrung nach ist es nie so schön, wenn es mit Aussagen auf die Realität einer Sprache trifft.

Es ist entweder fast so ausführlich, weil Sie Blöcke und Rückgaben benötigen, oder es kann nur Ausdrücke enthalten, sodass es für alle außer den einfachsten Dingen im Grunde nutzlos ist.

Anonyme Funktionen in Go sind so nah wie möglich am Optimum. Ich sehe keinen Wert darin, es noch weiter zu rasieren.

Das Problem ist nicht die func -Syntax, sondern die redundanten Typdeklarationen.

Einfach den Funktionsliteralen zu erlauben, eindeutige Typen zu eliminieren, würde viel bewirken. So verwenden Sie das Cap'n'Proto-Beispiel:

s.Write(ctx, func(p) error { return p.SetData([]byte("Hello, ")) })

Ja, es sind die Typdeklarationen, die wirklich Lärm hinzufügen. Leider hat "func (p) error" schon eine Bedeutung. Vielleicht würde es funktionieren, _ zuzulassen, einen abgeleiteten Typ zu ersetzen?

s.Write(ctx, func(p _) _ { return p.SetData([]byte("Hello, ")) })

Ich mag das eher; überhaupt keine syntaktische Änderung erforderlich.

Ich mag das Stottern von _ nicht. Vielleicht könnte func durch ein Schlüsselwort ersetzt werden, das die Typparameter ableitet:
s.Write(ctx, λ(p) { return p.SetData([]byte("Hello, ")) })

Ist das eigentlich ein Vorschlag oder spuckst du nur aus, wie Go aussehen würde, wenn du es für Halloween wie Scheme anziehen würdest? Ich denke, dieser Vorschlag ist sowohl unnötig als auch im Einklang mit dem Fokus der Sprache auf Lesbarkeit.

Bitte hören Sie auf, die Syntax der Sprache zu ändern, nur weil sie anders aussieht als andere Sprachen.

Ich denke, dass eine prägnante Syntax für anonyme Funktionen in anderen Sprachen überzeugender ist, die sich mehr auf Callback-basierte APIs verlassen. Bei Go bin ich mir nicht sicher, ob sich die neue Syntax wirklich auszahlen würde. Es ist nicht so, dass es nicht viele Beispiele gibt, wo Leute anonyme Funktionen verwenden, aber zumindest in dem Code, den ich lese und schreibe, ist die Häufigkeit ziemlich gering.

Ich denke, dass eine prägnante Syntax für anonyme Funktionen in anderen Sprachen überzeugender ist, die sich mehr auf Callback-basierte APIs verlassen.

Bis zu einem gewissen Grad ist dies eine sich selbst verstärkende Bedingung: Wenn es einfacher wäre, prägnante Funktionen in Go zu schreiben, sehen wir möglicherweise APIs im funktionaleren Stil. (Ob das gut ist oder nicht, weiß ich nicht.)

Ich möchte betonen, dass es einen Unterschied zwischen "funktionalen" und "Rückruf"-APIs gibt: Wenn ich "Rückruf" höre, denke ich an "asynchroner Rückruf", was zu einer Art Spaghetti-Code führt, den wir glücklicherweise vermeiden konnten Gehen. Synchrone APIs (wie filepath.Walk oder strings.TrimFunc ) sind wahrscheinlich der Anwendungsfall, den wir im Auge behalten sollten, da diese besser zum synchronen Stil von Go-Programmen im Allgemeinen passen.

Ich möchte hier nur eingreifen und einen Anwendungsfall anbieten, bei dem ich die Lambda-Syntax im arrow -Stil zu schätzen gelernt habe, um die Reibung erheblich zu reduzieren: Currying.

Erwägen:

// current syntax
func add(a int) func(int) int {
    return func(b int) int {
        return a + b
    }
}

// arrow version (draft syntax, of course)
add := (a int) => (b int) => a + b

func main() {
    add2 := add(2)
    add3 := add(3)
    fmt.Println(add2(5), add3(6))
}

Stellen Sie sich nun vor, wir versuchen, einen Wert in mongo.FieldConvertFunc oder etwas zu verwandeln, das einen funktionalen Ansatz erfordert, und Sie werden sehen, dass eine leichtere Syntax die Dinge erheblich verbessern kann, wenn eine Funktion nicht mehr gecurry wird zum Curry (gerne kann ich ein realeres Beispiel geben, wenn jemand möchte).

Nicht überzeugt? Dachte nicht. Ich liebe auch die Einfachheit von go und denke, dass es schützenswert ist.

Eine andere Situation, die mir oft passiert, ist, wo Sie haben und jetzt den nächsten Streit mit Curry würzen wollen.

jetzt müsstest du dich umziehen
func (a, b) x
zu
func (a) func(b) x { return func (b) { return ...... x } }

Wenn es eine Pfeilsyntax gäbe, würden Sie sie einfach ändern
(a, b) => x
zu
(a) => (b) => x

@neild Obwohl ich noch nicht zu diesem Thread beigetragen habe, habe ich einen anderen Anwendungsfall, der von etwas Ähnlichem wie dem, was Sie vorgeschlagen haben, profitieren würde.

Aber in diesem Kommentar geht es eigentlich um eine andere Möglichkeit, mit der Ausführlichkeit beim Aufrufen von Code umzugehen: Lassen Sie ein Tool wie gocode (oder ähnliches) einen Funktionswert für Sie erstellen.

Nimm dein Beispiel:

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

Wenn wir davon ausgehen, dass wir Folgendes eingegeben haben:

var _ = compute(
                ^

mit dem Cursor an der Position, die durch das ^ angezeigt wird; dann könnte das Aufrufen eines solchen Tools trivialerweise einen Funktionswert für Sie vorgeben:

var _ = compute(func(a, b float64) float64 { })
                                            ^

Das würde sicherlich den Anwendungsfall abdecken, den ich im Sinn hatte; deckt es deine ab?

Code wird viel häufiger gelesen als geschrieben. Ich glaube nicht, dass es sich lohnt, ein wenig Tipparbeit zu sparen, um hier die Sprachsyntax zu ändern. Der Vorteil, falls es einen gibt, würde größtenteils darin bestehen, den Code lesbarer zu machen. Der Editor-Support wird dabei nicht helfen.

Eine Frage ist natürlich, ob das Entfernen der vollständigen Typinformationen aus einer anonymen Funktion die Lesbarkeit fördert oder beeinträchtigt.

Ich glaube nicht, dass diese Art von Syntax die Lesbarkeit verringert, fast alle modernen Programmiersprachen haben eine Syntax für dies und das, weil sie die Verwendung von funktionalem Stil fördert, um die Textbausteine ​​zu reduzieren und den Code klarer und einfacher zu warten. Es ist sehr mühsam, anonyme Funktionen in Golang zu verwenden, wenn sie als Parameter an Funktionen übergeben werden, da Sie sich wiederholen müssen, indem Sie die Typen erneut eingeben, von denen Sie wissen, dass Sie sie übergeben müssen.

Ich unterstütze den Vorschlag. Es spart Tipparbeit und hilft bei der Lesbarkeit. Mein Anwendungsfall,

// Type definitions and functions implementation.
type intSlice []int
func (is intSlice) Filter(f func(int) bool) intSlice { ... }
func (is intSlice) Map(f func(int) int) intSlice { ... }
func (is intSlice) Reduce(f func(int, int) int) int { ...  }
list := []int{...} 
is := intSlice(list)

ohne leichtgewichtige anonyme Funktionssyntax:

res := is.Map(func(i int)int{return i+1}).Filter(func(i int) bool { return i % 2 == 0 }).
             Reduce(func(a, b int) int { return a + b })

mit leichtgewichtiger anonymer Funktionssyntax:

res := is.Map((i) => i+1).Filter((i)=>i % 2 == 0).Reduce((a,b)=>a+b)

Das Fehlen prägnanter anonymer Funktionsausdrücke macht Go weniger lesbar und verstößt gegen das DRY-Prinzip. Ich würde gerne Funktions-/Callback-APIs schreiben und verwenden, aber die Verwendung solcher APIs ist unausstehlich ausführlich, da jeder API-Aufruf entweder eine bereits definierte Funktion oder einen anonymen Funktionsausdruck verwenden muss, der Typinformationen wiederholt, die aus dem Kontext klar hervorgehen sollten (if die API ist richtig entworfen).

Mein Wunsch für diesen Vorschlag ist nicht im Entferntesten, dass ich denke, dass Go wie andere Sprachen aussehen oder sein sollte. Mein Verlangen wird ausschließlich von meiner Abneigung getrieben, mich zu wiederholen und unnötigen syntaktischen Lärm einzufügen.

In Go weicht die Syntax für Funktionsdeklarationen etwas von dem regulären Muster ab, das wir für andere Deklarationen haben. Für Konstanten, Typen, Variablen gilt immer:

keyword name type value

Beispielsweise:

const   c    int  = 0
type    t    foo
var     v    bool = true

Im Allgemeinen kann der Typ ein Literaltyp oder ein Name sein. Für Funktionen, die dies aufschlüsseln, muss der Typ immer eine wörtliche Signatur sein. Man könnte sich so etwas vorstellen:

type BinaryOp func(x, y Value) Value

func f BinaryOp { ... }

wobei der Funktionstyp als Name angegeben wird. Etwas erweitert könnte man dann vielleicht eine BinaryOp Closure schreiben

BinaryOp{ return x.Add(y) }

was zu einer kürzeren Abschlussnotation führen könnte. Zum Beispiel:

vector.Apply(BinaryOp{ return x.Add(y) })

Der Hauptnachteil besteht darin, dass Parameternamen nicht mit der Funktion deklariert werden. Die Verwendung des Funktionstyps bringt sie in den Gültigkeitsbereich, ähnlich wie die Verwendung eines Strukturwerts x des Typs S ein Feld f in einem Auswahlausdruck x.f in den Gültigkeitsbereich bringt S{f: "foo"} .

Außerdem erfordert dies einen explizit deklarierten Funktionstyp, was möglicherweise nur dann sinnvoll ist, wenn dieser Typ sehr häufig vorkommt.

Nur eine andere Perspektive für diese Diskussion.

Lesbarkeit steht an erster Stelle, darauf können wir uns wohl alle einigen.

Aber eine Sache, auf die ich mich einlassen möchte (da es nicht so aussieht, als hätte es jemand anderes explizit gesagt), ist, dass die Frage der Lesbarkeit immer davon abhängen wird, woran Sie gewöhnt sind. Eine Diskussion darüber zu führen, ob es der Lesbarkeit schadet oder schadet, führt meiner Meinung nach nicht weiter.

@griesemer vielleicht wäre hier eine Perspektive aus deiner Zeit bei der Arbeit an V8 hilfreich. Ich kann (zumindest) sagen, dass ich mit der früheren Syntax von Javascript für Funktionen ( function(x) { return x; } ) sehr zufrieden war, die (in gewisser Weise) noch schwerer zu lesen war als die von Go im Moment. Ich war in @douglascrockfords „Diese neue Syntax ist Zeitverschwendung“-Camp.

Aber trotzdem _passierte_ die Pfeilsyntax und ich akzeptierte sie _weil ich musste_. Heute jedoch, nachdem ich es viel häufiger verwendet habe und mich damit vertrauter gemacht habe, kann ich sagen, dass es der Lesbarkeit enorm hilft . Ich habe den Fall von Currying verwendet (und @hooluupog brachte einen ähnlichen Fall von "Punktverkettung" zur Sprache), bei dem eine leichtgewichtige Syntax Code erzeugt, der leichtgewichtig ist, ohne übermäßig clever zu sein.

Wenn ich jetzt Code sehe, der Dinge wie x => y => z => ... macht und auf einen Blick viel einfacher zu verstehen ist (wieder ... weil ich damit _vertraut_ bin. Vor nicht allzu langer Zeit hatte ich das genaue Gegenteil).

Was ich sagen will ist: Diese Diskussion läuft darauf hinaus:

  1. Wenn Sie nicht daran gewöhnt sind, erscheint es _wirklich_ seltsam und grenzwertig nutzlos, wenn nicht sogar schädlich für die Lesbarkeit. Manche Leute haben oder haben einfach keine Gefühle dazu.
  2. Je funktionaler Sie programmieren, desto mehr drängt sich die Notwendigkeit einer solchen Syntax auf. Ich würde vermuten, dass dies etwas mit funktionalen Konzepten (wie partielle Anwendung und Curry) zu tun hat, die viele Funktionen für winzige Jobs einführen, was für den Leser zu Lärm führt.

Das Beste, was wir tun können, ist, mehr Anwendungsfälle bereitzustellen.

Als Antwort auf den Kommentar von @dimitropoulos ist hier eine grobe Zusammenfassung meiner Ansicht:

Ich möchte Entwurfsmuster (z. B. funktionale Programmierung) verwenden, die von diesem Vorschlag stark profitieren würden, da ihre Verwendung mit der aktuellen Syntax übermäßig ausführlich ist.

@dimitropoulos Ich habe in Ordnung an V8 gearbeitet, aber das war das Erstellen der virtuellen Maschine, die in C++ geschrieben wurde. Meine Erfahrung mit aktuellem Javascript ist begrenzt. Allerdings ist Javascript eine dynamisch typisierte Sprache, und ohne Typen entfällt ein Großteil der Eingabe. Wie mehrere Leute bereits angesprochen haben, ist ein Hauptproblem hier die Notwendigkeit, Typen zu wiederholen, ein Problem, das in Javascript nicht existiert.

Außerdem, fürs Protokoll: In den frühen Tagen des Go-Designs haben wir uns tatsächlich mit der Pfeilsyntax für Funktionssignaturen beschäftigt. Ich erinnere mich nicht an die Details, aber ich bin mir ziemlich sicher, dass Notationen wie z

func f (x int) -> float32

stand auf der weißen Tafel. Schließlich haben wir den Pfeil fallen gelassen, weil er mit mehreren (Nicht-Tupel-) Rückgabewerten nicht so gut funktionierte; und sobald func und die Parameter vorhanden waren, war der Pfeil überflüssig; vielleicht "hübsch" (wie beim mathematischen Suchen), aber immer noch überflüssig. Es schien auch eine Syntax zu sein, die zu einer "anderen" Art von Sprache gehörte.

Aber Closures in einer leistungsfähigen Allzwecksprache öffneten die Türen zu neuen, funktionaleren Programmierstilen. Jetzt, 10 Jahre später, könnte man es aus einem anderen Blickwinkel betrachten.

Dennoch denke ich, dass wir hier sehr vorsichtig sein müssen, um keine spezielle Syntax für Closures zu erstellen. Was wir jetzt haben, ist einfach und regelmäßig und hat bisher gut funktioniert. Was auch immer der Ansatz ist, wenn es eine Änderung gibt, muss es meiner Meinung nach regelmäßig sein und für jede Funktion gelten.

In Go weicht die Syntax für Funktionsdeklarationen etwas von dem regulären Muster ab, das wir für andere Deklarationen haben. Für Konstanten, Typen, Variablen gilt immer:
keyword name type value
[…]
Für Funktionen, die dies aufschlüsseln, muss der Typ immer eine wörtliche Signatur sein.

Beachten Sie, dass wir für Parameterlisten und const - und var -Deklarationen ein ähnliches Muster haben, IdentifierList Type , das wir wahrscheinlich auch beibehalten sollten. Das scheint, als würde es das : -Token im Lambda-Kalkül-Stil ausschließen, um Variablennamen von Typen zu trennen.

Was auch immer der Ansatz ist, wenn es eine Änderung gibt, muss es meiner Meinung nach regelmäßig sein und für jede Funktion gelten.

Das Muster keyword name type value ist für _declarations_, aber die Anwendungsfälle, die @neild erwähnt, sind alle für _literals_.

Wenn wir uns mit dem Problem der Literale befassen, dann glaube ich, dass das Problem der Deklarationen trivial wird. Für Deklarationen von Konstanten, Variablen und jetzt Typen erlauben (oder verlangen) wir ein = Token vor dem value . Es scheint, als wäre es einfach genug, das auf Funktionen zu erweitern:

FunctionDecl = "func" ( FunctionSpec | "(" { FunctionSpec ";" } ")" ).
FunctionSpec = FunctionName Function |
               IdentifierList (Signature | [ Signature ] "=" Expression) .

FunctionLit = "func" Function | ShortFunctionLit .
ShortParameterList = ShortParameterDecl { "," ShortParameterDecl } .
ShortParameterDecl = IdentifierList [ "..." ] [ Type ] .

Der Ausdruck nach dem Token = muss ein Funktionsliteral sein oder vielleicht eine Funktion, die von einem Aufruf zurückgegeben wird, dessen Argumente alle zur Kompilierzeit verfügbar sind. In der Form = Signature angegeben werden, um die Deklarationen des Argumenttyps vom Literal zum FunctionSpec zu verschieben.

Beachten Sie, dass der Unterschied zwischen einem ShortParameterDecl und dem vorhandenen ParameterDecl darin besteht, dass einzelne IdentifierList s als Parameternamen und nicht als Typen interpretiert werden.


Beispiele

Betrachten Sie diese heute akzeptierte Funktionsdeklaration:

func compute(f func(x, y float64) float64) float64 { return f(3, 4) }

Wir könnten das entweder zusätzlich zu den Beispielen unten beibehalten (z. B. für Go 1-Kompatibilität) oder die Function -Produktion eliminieren und nur die ShortFunctionLit -Version verwenden.

Für verschiedene ShortFunctionLit -Optionen ergibt die oben vorgeschlagene Grammatik:

Rostartig:

ShortFunctionLit = "|" ShortParameterList "|" Block .

Gibt Folgendes zu:

func compute = |f func(x, y float64) float64| { f(3, 4) }
func compute(func (x, y float64) float64) float64 = |f| { f(3, 4) }



md5-c712da47cbcf3d0379ff810dfd76ce59



```go
func (
    compute(func (x, y float64) float64) float64 = |f| { f(3, 4) }
)



md5-8a4d86e5ac5f718d8d35839eaf9f1029



ShortFunctionLit = "(" ShortParameterList ")" "=>" Expression .



md5-e429c4db0e2a76fe83f1f524910c0075



```go
func compute(func (x, y float64) float64) float64 = (f) => f(3, 4)



md5-bcb7677c087284f6121b65ce14d46d93



```go
func (
    compute(func (x, y float64) float64) float64 = (f) => f(3, 4)
)



md5-bf0cf8ca5f55bbedf92dc2047d871378



ShortFunctionLit = "λ" ShortParameterList "." Expression .



md5-3c1a0d273a1aee09721883f5be8fcfce



```go
func compute(func (x, y float64) float64) float64) = λf.f(3, 4)



md5-87735958588cf5a763da8a89d1f9a675



```go
func (
    compute(func (x, y float64) float64) float64) = λf.f(3, 4)
)



md5-d613a37ac429244205560535e5401d63



ShortFunctionLit = "\" ShortParameterList "->" Expression .



md5-95523002741f1036dff7837c1701336d



```go
func compute(func (x, y float64) float64) float64) = \f -> f(3, 4)



md5-818e7097669fe3bc7a333787735e5657



```go
func (
    compute(func (x, y float64) float64) float64) = \f -> f(3, 4)
)



md5-af63df358fad8d4beffd23e2d0c337a4



ShortFunctionLit = "[" ShortParameterList "]" Block .



md5-f66b9b33e7dca8cce60726de14cfc931



```go
func compute(func (x, y float64) float64) float64) = [f] { f(3, 4) }



md5-13e2e0ab357ce95a5a0e2fbd930ba841



```go
func (
    compute(func (x, y float64) float64) float64) = [f] { f(3, 4) }
)

Persönlich finde ich alle bis auf die Scala-ähnlichen Varianten ziemlich lesbar. (In meinen Augen ist die Scala-ähnliche Variante zu stark auf Klammern: Dadurch sind die Zeilen viel schwieriger zu scannen.)

Persönlich interessiert mich das hauptsächlich, wenn ich die Parameter- und Ergebnistypen weglassen kann, wenn sie abgeleitet werden können. Ich bin sogar in Ordnung mit der aktuellen Funktionsliteral-Syntax, wenn ich das tun kann. (Dies wurde oben diskutiert.)

Zugegebenermaßen widerspricht das dem Kommentar von @griesemer .

Was auch immer der Ansatz ist, wenn es eine Änderung gibt, muss es meiner Meinung nach regelmäßig sein und für jede Funktion gelten.

Ich kann dem nicht ganz folgen. Funktionsdeklarationen müssen unbedingt die vollständigen Typinformationen für die Funktion enthalten, da es keine Möglichkeit gibt, sie mit ausreichender Genauigkeit aus dem Funktionskörper abzuleiten. (Das gilt natürlich nicht für alle Sprachen, aber für Go.)

Funktionsliterale hingegen könnten Typinformationen aus dem Kontext ableiten.

@neild Entschuldigung für die Ungenauigkeit: Was ich mit diesem Satz meinte, ist, dass er, wenn es eine neue, unterschiedliche Syntax (Pfeile oder was auch immer) gäbe, etwas regelmäßig sein und überall gelten sollte. Wenn es möglich ist, dass Typen weggelassen werden können, wäre das wieder orthogonal.

@griesemer Danke; Da stimme ich (meistens) zu.

Ich denke, die interessante Frage für diesen Vorschlag ist, ob es eine gute Idee ist, eine Syntax zu haben oder nicht; was diese Syntax wäre, ist wichtig, aber relativ trivial.

Allerdings kann ich der Versuchung nicht widerstehen, meinen eigenen Vorschlag ein wenig zu bikeshed.

var sum func(int, int) int = func a, b { return a + b }

Der Vorschlag von @neild fühlt sich für mich richtig an. Es kommt der bestehenden Syntax ziemlich nahe, funktioniert aber für die funktionale Programmierung, da es die Wiederholung der Typspezifikationen eliminiert. Es ist nicht _so_ viel weniger kompakt als (a, b) => a + b und passt gut in die bestehende Syntax.

@neild

var sum func(int, int) int = func a, b { return a + b }

Würde das eine Variable oder eine Funktion deklarieren? Wenn es sich um eine Variable handelt, wie würde die entsprechende Funktionsdeklaration aussehen?

Unter meinem obigen Deklarationsschema wäre es, wenn ich es richtig verstehe:

ShortFunctionLit = "func" ShortParameterList Block .
func compute = func f func(x, y float64) float64 { return f(3, 4) }
func compute(func (x, y float64) float64) float64 = func f { return f(3, 4) }
func (
    compute = func f func(x, y float64) float64 { return f(3, 4) }
)
func (
    compute(func (x, y float64) float64) float64 = func f { return f(3, 4) }
)

Ich glaube nicht, dass ich ein Fan bin: Es stottert ein bisschen auf func und scheint keine ausreichende visuelle Unterbrechung zwischen dem func -Token und den folgenden Parametern zu bieten.

Oder würden Sie Klammern aus der Deklaration weglassen, anstatt sie Literalen zuzuweisen?

func compute f func(x, y float64) float64 { return f(3, 4) }

Ich mag immer noch nicht den Mangel an visueller Unterbrechung, obwohl ...

Würde das eine Variable oder eine Funktion deklarieren? Wenn es sich um eine Variable handelt, wie würde die entsprechende Funktionsdeklaration aussehen?

Eine Variable. Die äquivalente Funktionsdeklaration wäre vermutlich func sum a, b { return a+b } , aber das wäre aus offensichtlichen Gründen ungültig – Sie können Parametertypen nicht aus Funktionsdeklarationen ausschließen.

Die Grammatikänderung, an die ich denke, wäre so etwas wie:

ShortFunctionLit = "func" [ IdentifierList ] [ "..." ] FunctionBody .

Ein kurzes Funktionsliteral unterscheidet sich von einem regulären Funktionsliteral dadurch, dass die Klammern in der Parameterliste weggelassen werden, es definiert nur die Namen der eingehenden Parameter und nicht die ausgehenden Parameter. Die Typen der eingehenden Parameter und die Typen und Anzahl der ausgehenden Parameter werden aus dem umgebenden Kontext abgeleitet.

Ich glaube nicht, dass es notwendig ist, die Angabe optionaler Parametertypen in einem kurzen Funktionsliteral zuzulassen. Sie verwenden in diesem Fall einfach ein reguläres Funktionsliteral.

Wie @ianlancetaylor betonte, ist die leichte Notation wirklich nur dann sinnvoll, wenn sie das Weglassen von Parametertypen zulässt, da sie leicht abgeleitet werden können. Daher ist der Vorschlag von @neild der beste und einfachste, den ich bisher gesehen habe. Was es jedoch nicht ohne Weiteres zulässt, ist eine leichtgewichtige Notation für Funktionsliterale, die auf benannte Ergebnisparameter verweisen wollen. Aber vielleicht sollten sie in diesem Fall die vollständige Notation verwenden. (Es ist nur ein bisschen unregelmäßig).

Wir könnten sogar (x, y) { ... } als Kurzform für func (x, y T) T { ... } parsen; obwohl es ein wenig Parser-Vorausschau erfordern würde, aber vielleicht nicht so schlimm.

Als Experiment habe ich gofmt modifiziert, um Funktionsliterale in die kompakte Syntax umzuschreiben, und es gegen src/ ausgeführt. Die Ergebnisse könnt ihr hier sehen:

https://github.com/neild/go/commit/2ff18c6352788aa8f8cbe8b5d5d4c73956ca7c6f

Ich habe keinen Versuch unternommen, dies auf Fälle zu beschränken, in denen es Sinn macht; Ich wollte nur ein Gefühl dafür bekommen, wie sich die kompakte Syntax in der Praxis auswirken könnte. Ich habe mich noch nicht genug damit beschäftigt, um mir eine Meinung zu den Ergebnissen zu bilden.

@neild Schöne Analyse. Einige Beobachtungen:

  1. Der Bruchteil der Fälle, in denen das Funktionsliteral mit := gebunden ist, ist enttäuschend, da die Behandlung dieser Fälle ohne explizite Typanmerkungen einen komplizierteren Inferenzalgorithmus erfordern würde.

  2. Die an Rückrufe übergebenen Literale sind in einigen Fällen einfacher zu lesen, in anderen jedoch schwieriger.
    Beispielsweise ist es etwas unglücklich, die Informationen zum Rückgabetyp für Funktionsliterale zu verlieren, die sich über viele Zeilen erstrecken, da dies dem Leser auch mitteilt, ob er eine funktionale oder eine zwingende API betrachtet.

  3. Die Reduzierung der Boilerplate für Funktionsliterale innerhalb von Slices ist erheblich.

  4. defer - und go -Anweisungen sind ein interessanter Fall: Würden wir die Argumenttypen aus den tatsächlich an die Funktion übergebenen Argumenten ableiten?

  5. Ein paar nachgestellte ... Tokens fehlen in den Beispielen.

defer und go sind in der Tat ein recht interessanter Fall.

go func p {
  // do something with p
}("parameter")

Würden wir den Typ von p aus dem eigentlichen Funktionsparameter ableiten? Dies wäre für go -Anweisungen ganz nett, obwohl Sie natürlich den gleichen Effekt erzielen können, indem Sie einfach einen Abschluss verwenden:

p := "parameter"
go func() {
  // do something with p
}()

Ich würde das voll und ganz unterstützen. Ehrlich gesagt ist es mir egal, wie sehr es "wie in anderen Sprachen aussieht", ich möchte nur eine weniger ausführliche Art und Weise, anonyme Funktionen zu verwenden.

BEARBEITEN: Ausleihen der zusammengesetzten Literal-Syntax ...

type F func(int) float64
var f F
f = F {      (i) (o) { o = float64(i); return } }
f = F {      (i) o   { o = float64(i); return } } // single return value
f = F { func (i) o   { o = float64(i); return } } // +func for good measure?

Nur eine Idee:
So würde das Beispiel von OP mit einem _nicht typisierten Funktionsliteral_ mit Swifts Syntax aussehen:

compute({ $0 + $1 })

Ich glaube, dies hätte den Vorteil, dass es vollständig abwärtskompatibel mit Go 1 wäre.

Ich habe das gerade gefunden, weil ich eine einfache TCP-Chat-App geschrieben habe,
Im Grunde habe ich eine Struktur mit einer Scheibe darin

type connIndex struct {
    conns []net.Conn
    mu    sync.Mutex
}

und ich möchte einige Operationen gleichzeitig darauf anwenden (Hinzufügen von Verbindungen, Senden von Nachrichten an alle usw.)

und anstatt dem normalen Pfad des Kopierens und Einfügens des Mutex-Sperrcodes zu folgen oder eine Daemon-Goroutine zu verwenden, um den Zugriff zu verwalten, dachte ich, ich würde einfach eine Schließung übergeben

func (c *connIndex) run(f func([]net.Conn)) {
    c.mu.Lock()
    defer c.mu.Unlock()
    f(c.conns)
}

für kurze Operationen ist es übermäßig ausführlich (immer noch besser als lock und defer unlock() )

conns.run(func(conns []net.Conn) { conns = append(conns, conn) })

Dies verstößt gegen das DRY-Prinzip, da ich genau diese Funktionssignatur in der Methode run eingegeben habe.

Wenn das Ableiten der Funktionssignatur unterstützt wird, könnte ich es so schreiben

conns.run(func(conns) { conns = append(conns, conn) })

Ich glaube nicht, dass dies den Code weniger lesbar macht. Sie können anhand von append erkennen, dass es sich um einen Slice handelt, und da ich meine Variablen gut benannt habe, können Sie erraten, dass es sich um eine []net.Conn handelt, ohne hinzusehen bei der Methodensignatur run .

Ich würde es vermeiden, die Typen von Parametern basierend auf dem Funktionskörper abzuleiten, sondern nur für Fälle, in denen dies offensichtlich ist (z. B. das Übergeben von Abschlüssen an Funktionen), Rückschlüsse hinzufügen.

Ich würde sagen, dies schadet der Lesbarkeit nicht, da es dem Leser eine Option gibt, wenn er den Typ des Parameters nicht kennt, kann er ihn godef oder darüber bewegen und den Editor dazu bringen, ihn ihm zu zeigen .

So ähnlich wie in einem Buch wiederholen sie die Einführung der Charaktere nicht, außer dass wir eine Schaltfläche hätten, um sie anzuzeigen / dorthin zu springen.

Ich bin schlecht im Schreiben, also hoffe ich, dass du es überlebt hast, das zu lesen :)

Ich denke, dies ist überzeugender, wenn wir seine Verwendung auf Fälle beschränken, in denen der Funktionskörper ein einfacher Ausdruck ist.

Ich wage zu widersprechen. Dies würde immer noch zu zwei Möglichkeiten führen, eine Funktion zu definieren, und einer der Gründe, warum ich mich in Go verliebt habe, ist, dass es zwar hier und da etwas ausführlich ist, aber eine erfrischende Ausdruckskraft hat: Sie sehen, wo ein Abschluss ist, weil es einen gibt entweder ein Schlüsselwort func oder der Parameter ist eine Funktion, wenn Sie ihn verfolgen.

conns.run(func(conns []net.Conn) { conns = append(conns, conn) })
Dies verstößt gegen das DRY-Prinzip, da ich genau diese Funktionssignatur in der run-Methode eingegeben habe.

DRY _is_ wichtig, kein Zweifel. Aber es auf jeden Teil der Programmierung anzuwenden, um das Prinzip auf Kosten der Fähigkeit, den Code mit dem geringstmöglichen Aufwand zu verstehen, aufrechtzuerhalten, schießt meiner Meinung nach ein bisschen über das Ziel hinaus.

Ich denke, das allgemeine Problem hier (und einiger anderer Vorschläge) ist, dass es in der Diskussion hauptsächlich darum geht, wie man den Aufwand beim _Schreiben_ des Codes spart, während es meiner Meinung nach darum gehen sollte, wie man den Aufwand beim _Lesen_ des Codes sichert. Jahre nachdem man es geschrieben hat. Ich habe kürzlich einen poc.pl von mir bekommen und versuche immer noch herauszufinden, was es tut ... ;)

conns.run(func(conns) { conns = append(conns, conn) })
Ich glaube nicht, dass dies den Code weniger lesbar macht, Sie können aufgrund von append erkennen, dass es sich um einen Slice handelt, und da ich meine Variablen gut benannt habe, können Sie erraten, dass es sich um eine []net.Conn handelt, ohne die Signatur der run-Methode zu betrachten .

Aus meiner Sicht gibt es mehrere Probleme mit dieser Aussage. Ich weiß nicht, wie andere es sehen, aber ich _hasse_ Vermutungen. Man kann Recht haben, man kann sich irren, aber man muss sich sicherlich anstrengen - zugunsten des Sparens []net.Conn „eintippen“. Und die Lesbarkeit sowie die Verständlichkeit von Code sollten durch gute Variablennamen unterstützt werden, nicht darauf aufbauen.

Abschließend: Ich denke, der Fokus der Diskussion sollte sich von der Reduzierung kleinerer Anstrengungen beim Schreiben von Code auf die Reduzierung der Bemühungen zum Verständnis des Codes verlagern.

Ich schließe mit einem Zitat von Dave Cheney, das Robert Pike zitiert (iirc)

Klar ist besser als schlau.

Das mühsame Abtippen von Funktionssignaturen kann durch die automatische Vervollständigung etwas erleichtert werden. Zum Beispiel bietet gopls Vervollständigungen an, die Funktionsliterale erzeugen:
cb

Ich denke, dies bietet einen guten Mittelweg, wenn die Typnamen noch im Quellcode enthalten sind, es nur eine Möglichkeit gibt, eine anonyme Funktion zu definieren, und Sie nicht die gesamte Signatur eingeben müssen.

wird das hinzugefügt oder nicht?
... wer diese Funktion nicht mag, kann immer noch die alte Syntax verwenden.
... für uns, die eine bessere Einfachheit wünschen, können wir diese neue Funktion hoffentlich nutzen, es ist 1 Jahr her, seit ich go geschrieben habe, ich bin mir nicht sicher, ob die Community dies immer noch für wichtig hält,
... wird dies hinzugefügt oder nicht?

@noypi Es wurde keine Entscheidung getroffen. Diese Frage bleibt offen.

https://golang.org/wiki/NoPlusOne

Ich unterstütze diesen Vorschlag und denke, dass diese Funktion in Verbindung mit Generika die funktionale Programmierung in Go entwicklerfreundlicher machen würde.

Hier ist, was ich sehen möchte, ungefähr:

type F func(int, int) int

// function declaration
f := F (x, y) { return x * y}

// function passing 
// g :: func(F)
g((x, y) { return x * y })

// returning function
func h() F {
    return (x, y) { return x * y }
}

Ich würde gerne (a, b) => a * b und weitermachen können.

Ich kann nicht glauben, dass Pfeilfunktionen in Go lang immer noch nicht verfügbar sind.
Es ist erstaunlich, wie übersichtlich und einfach mit Javascript zu arbeiten ist.

JavaScript kann dies trivial implementieren, da es sich nicht um die Parameter, ihre Anzahl, die Werte oder ihre Typen kümmert, bis sie tatsächlich verwendet werden.

Die Möglichkeit, Typen in Funktionsliteralen wegzulassen, würde bei dem funktionalen Stil, den ich für die Gio-Layout-API verwende, sehr hilfreich sein. Siehe die vielen "func() {...}"-Literale in https://git.sr.ht/~eliasnaur/gio/tree/master/example/kitchen/kitchen.go? Ihre eigentliche Unterschrift hätte so etwas wie sein sollen

func(gtx layout.Context) layout.Dimensions

aber wegen der langen Typnamen ist gtx ein Zeiger auf ein gemeinsames layout.Context , das die eingehenden und ausgehenden Werte von jedem Funktionsaufruf enthält.

Ich werde wahrscheinlich unabhängig von diesem Problem zu den längeren Signaturen wechseln, um Klarheit und Korrektheit zu gewährleisten. Trotzdem glaube ich, dass mein Fall ein guter Erfahrungsbericht zur Unterstützung kürzerer Funktionsliterale ist.

PS Ein Grund, warum ich zu den längeren Signaturen neige, ist, dass sie durch Typaliase gekürzt werden können:

type C = layout.Context
type D = layout.Dimensions

was die Literale auf func(gtx C) D { ... } verkürzt.

Ein zweiter Grund ist, dass die längeren Signaturen aufwärtskompatibel sind mit allem, was dieses Problem löst.

Ich kam mit einer Idee hierher und stellte fest, dass @networkimprov hier bereits etwas Ähnliches vorgeschlagen hatte .

Mir gefällt die Idee, einen Funktionstyp (es könnte auch ein unbenannter Funktionstyp oder Alias ​​sein) als Bezeichner für ein Funktionsliteral zu verwenden, weil wir so die üblichen Typschlussregeln für Parameter und Rückgabewerte verwenden können, weil wir die kennen genaue Typen im Voraus. Dies bedeutet, dass (zum Beispiel) die automatische Vervollständigung wie gewohnt funktionieren kann und wir keine verrückten Top-Down-Typinferenzregeln einführen müssten.

Gegeben:

type F func(a, b int) int

mein ursprünglicher gedanke war:

F(a, b){return a + b}

aber das sieht zu sehr nach einem normalen Funktionsaufruf aus - es sieht nicht so aus, als würden dort a und b definiert.

Andere Möglichkeiten auswerfen (ich mag keine davon besonders):

F->(a, b){return a + b}
F::(a, b){return a + b}
(a, b := F){ return a + b }
F{a, b}{return a + b}
F{a, b: return a + b}
F{a, b; return a + b}

Vielleicht lauert hier irgendwo eine nette Syntax :)

Ein wichtiger Punkt der zusammengesetzten Literalsyntax ist, dass sie keine Typinformationen im Parser benötigt. Die Syntax für Strukturen, Arrays, Slices und Maps ist identisch; der Parser muss den Typ von T nicht kennen, um einen Syntaxbaum für T{...} $ zu generieren.

Ein weiterer Punkt ist, dass die Syntax auch kein Backtracking im Parser erfordert. Wenn es Mehrdeutigkeit gibt, ob ein { Teil eines zusammengesetzten Literals oder eines Blocks ist, wird diese Mehrdeutigkeit immer zugunsten des letzteren aufgelöst.

Ich mag immer noch die Syntax, die ich an anderer Stelle in dieser Ausgabe vorgeschlagen habe, die jede Parser-Mehrdeutigkeit vermeidet, indem sie das Schlüsselwort func beibehält:

func a, b { return a + b }

Ich habe mein :-1: entfernt. Ich bin immer noch nicht :+1: dabei, aber ich überdenke meine Position. Generika werden zu einer Zunahme von Kurzfunktionen wie genericSorter(slice, func(a, b T) bool { return a > b }) führen. Ich fand auch https://github.com/golang/go/issues/37739#issuecomment -624338848 überzeugend.

Es werden zwei Hauptwege diskutiert, um Funktionsliterale prägnanter zu machen:

  1. eine Kurzform für Körper, die einen Ausdruck zurückgeben
  2. Eliminieren der Typen in Funktionsliteralen.

Ich denke, beides sollte getrennt behandelt werden.

Wenn FunctionBody in etwas wie geändert wird

FunctionBody = Block | "->" ExpressionBody
ExpressionBody = Expression | "(" ExpressionList ")"

das würde vor allem Funktionsliteralen mit oder ohne Typelision helfen und es auch ermöglichen, dass sehr einfache Funktions- und Methodendeklarationen auf der Seite leichter sind:

func (*T) Close() error -> nil

func (e *myErr) Unwrap() error -> e.err

func Alias(x int) -> anotherPackage.OriginalFunc(x)

func Id(type T)(x T) T -> x

func Swap(type T)(x, y T) -> (y, x)

(Godoc und Freunde konnten die Leiche immer noch verstecken)

Ich habe in diesem Beispiel die Syntax von @ianlancetaylor verwendet, deren größter Nachteil darin besteht, dass ein neues Token eingeführt werden muss (und eines, das in func(c chan T) -> <-c seltsam aussehen würde!), aber es könnte in Ordnung sein Verwenden Sie ein vorhandenes Token wie "=" wieder, wenn es keine Mehrdeutigkeit gibt. Ich werde "=" im Rest dieses Beitrags verwenden.

Für die Typelision gibt es zwei Fälle

  1. etwas, das immer funktioniert
  2. etwas, das nur in einem Kontext funktioniert, in dem die Typen abgeleitet werden können

Die Verwendung benannter Typen wie @griesemer vorgeschlagen würde immer funktionieren. Es scheint einige Probleme mit der Syntax zu geben. Ich bin sicher, das ließe sich regeln. Selbst wenn sie es wären, bin ich mir nicht sicher, ob es das Problem lösen würde. Es würde eine Verbreitung benannter Typen erfordern. Diese wären entweder in dem Paket, das den Ort definiert, an dem sie verwendet werden, oder sie müssten in jedem Paket definiert werden, das sie verwendet.

Bei ersterem bekommt man sowas

slices.Map(s, slices.MapFunc(x) = math.Abs(x-y))

und in letzterem bekommt man so etwas wie

type mf func(float64) float64
slices.Map(s, mf(x) = math.Abs(x-y))

In jedem Fall gibt es genug Unordnung, dass es die Boilerplate nicht wirklich einschränkt, es sei denn, jeder Name wird häufig verwendet.

Eine Syntax wie die von @neild konnte nur verwendet werden, wenn die Typen abgeleitet werden konnten. Eine einfache Methode wäre wie in #12854, listen Sie einfach jeden Kontext auf, in dem der Typ bekannt ist – Parameter einer Funktion, einem Feld zugewiesen, auf einem Kanal gesendet und so weiter. Der Go/Defer-Fall, den @neild angesprochen hat, scheint ebenfalls nützlich zu sein.

Dieser Ansatz lässt insbesondere Folgendes nicht zu

zero := func = 0
var f interface{} = func x, y = g(y, x)

aber das sind Fälle, in denen es sich lohnt, expliziter zu sein, selbst wenn es möglich wäre, den Typ algorithmisch abzuleiten, indem man untersucht, wo und wie diese verwendet werden.

Es erlaubt viele nützliche Fälle, einschließlich der nützlichsten/nachgefragtesten:

slices.Map(s, func x = math.Abs(x-y))
v := cond(useTls, FetchCertificate, func = nil)

In der Lage zu sein, einen Block unabhängig von der wörtlichen Syntax zu verwenden, ermöglicht auch:

http.HandleFunc("/bar", func w, r {
  // many lines ...
})

Das ist ein besonderer Fall, der mich zunehmend zu einem : +1:

Eine Frage, die ich nicht gesehen habe, ist, wie man mit ... -Parametern umgeht. Für beides könnte man argumentieren

f(func x, p = len(p))
f(func x, ...p = len(p))

Darauf habe ich keine Antwort.

@jimmyfrasche

  1. Eliminieren der Typen in Funktionsliteralen.

Ich glaube, dies sollte mit dem Hinzufügen von Funktionstyp-Literalen behandelt werden. Wobei der Typ „func“ ersetzt und die Argumenttypen ausgegeben werden (wie sie durch den Typ definiert sind). Dies behält die Lesbarkeit bei und ist ziemlich konsistent mit den Literalen für andere Typen.

http.Handle("/", http.HandlerFunc[w, r]{
    fmt.Fprinf(w, "Hello World")
})
  1. eine Kurzform für Körper, die einen Ausdruck zurückgeben

Refaktorieren Sie die Funktion als ihren eigenen Typ und dann werden die Dinge viel sauberer.

type ComputeFunc func(float64, float64) float64

func compute(fn ComputeFunc) float64 {
    return fn(3, 4)
}

compute(ComputeFunc[a,b]{return a + b})

Wenn Ihnen das zu ausführlich ist, geben Sie den Funktionstyp als Alias ​​in Ihren Code ein.

{
    type f = ComputeFunc

    compute(f[a,b]{return a + b})
}

Im Sonderfall einer Funktion ohne Argumente sollten die Klammern weggelassen werden.

type IntReturner func() int

fmt.Println(IntReturner{return 2}())

Ich wähle eckige Klammern, weil der Vertragsvorschlag bereits zusätzliche Standardklammern für generische Funktionen verwendet.

@Splizard Ich stehe zu dem Argument, dass dies nur die Unordnung aus der wörtlichen Syntax in viele zusätzliche Typdefinitionen verdrängen würde. Jede solche Definition müsste mindestens zweimal verwendet werden, bevor sie kürzer sein könnte, als nur die Typen in das Literal zu schreiben.

Ich bin mir auch nicht sicher, ob es in allen Fällen zu gut mit Generika funktionieren würde.

Betrachten Sie die ziemlich seltsame Funktion

func X(type T)(v T, func() T)

Sie könnten einen generischen Typ benennen, der mit X verwendet werden soll:

type XFunc(type T) func() T

Wenn nur die Definition von XFunc verwendet wird, um die Typen der Parameter abzuleiten, müssten Sie beim Aufrufen X angeben, welches T verwendet werden soll, obwohl dies von bestimmt wird der Typ von v :

X(v, XFunc(T)[] { /* ... */ })

Es könnte einen Sonderfall für Szenarien wie dieses geben, um T abzuleiten, aber dann würden Sie am Ende mit einem Großteil der Maschinerie enden, die für die Typelision in Funktionsliteralen erforderlich wäre.

Sie könnten auch einfach einen neuen Typ für jede T definieren, mit der Sie X aufrufen, aber dann gibt es nicht viel Einsparungen, es sei denn, Sie rufen X viele Male für jede T .

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen