Go: Vorschlag: String-Interpolation

Erstellt am 8. Sept. 2019  ·  67Kommentare  ·  Quelle: golang/go

Einführung

Für diejenigen, die nicht wissen, was es ist:

Schnell

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"

Kotlin

var age = 21

println("My Age Is: $age")

C

```c#
Zeichenfolgenname = "Markieren";
var date = DateTime.Now;

Console.WriteLine($"Hallo, {name}! Heute ist {date.DayOfWeek}, es ist jetzt {date:HH:mm}.");

# Reasoning of string interpolation vs old school formatting

I used to think it was a gimmick but it is not in fact. It is actually a way to provide type safety for string formatting. I mean compiler can expand interpolated strings into expressions and perform all kind of type checking needed.

### Examples

Variable := "var"
res := "123{variable}321" // res := "123" + variable + "321"


return errors.New("opening config file: \{err}") // return errors.New("opening config file: " + err.Error())


var status fmt.Stringer

msg := "Status beenden: {status}" // msg := "Status beenden: " + status.String()


v := 123
res := "value = {v}" // res := "value = " + someIntToStringConversionFunc(v)

# Syntax proposed

* Using `$` or `{}` would be more convenient in my opinion, but we can't use them for compatibility reasons
* Using Swift `\(…)` notation would be compatible but these `\()` are a bit too stealthy

I guess  `{…}` and `\(…)` can be combined into `\{…}`

So, the interpolation of variable `variable` into some string may look like

"{Variable}"

Formatting also has formatting options. It may look like

"{Variable[:]}"

#### Examples of options

v := 123,45
fmt.Println("value={v:04.3}") // value=0123.450


v := "Wert"
fmt.Println("value='{v:a50}'") // value='<45 Leerzeichen>Wert'

# Conversions

There should be conversions and formatting support for built in types and for types implementing `error` and `fmt.Stringer`. Support for types implementing

Typ Formatter-Schnittstelle {
Format(Formatstring) Zeichenfolge
}
```

kann später eingeführt werden, um Interpolationsoptionen zu behandeln

Vor- und Nachteile gegenüber herkömmlicher Formatierung

Vorteile

  • Geben Sie Sicherheit ein
  • Leistung (abhängig vom Compiler)
  • Unterstützung benutzerdefinierter Formatierungsoptionen für benutzerdefinierte Typen

Nachteile

  • Komplikation eines Compilers
  • Weniger Formatierungsmethoden werden unterstützt (kein %v (?), %T usw.)
Go2 LanguageChange Proposal

Hilfreichster Kommentar

Für mich ist Typprüfung als Grund, String-Interpolation in die Sprache aufzunehmen, nicht so überzeugend. Es gibt einen wichtigeren Grund, der nicht von der Reihenfolge abhängt, in der Sie die Variablen in fmt.Printf schreiben.

Nehmen wir eines der Beispiele aus der Vorschlagsbeschreibung und schreiben es in Go mit und ohne String-Interpolation:

  • Ohne String-Interpolation (aktuelles Go)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
  • Äquivalente Funktionalität mit String-Interpolation (und Mischen des @bradfitz- Kommentars, der benötigt wird, um die Formatierungsoptionen auszudrücken)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %{name}! Today is %{date.Weekday()}, it's %02{date.Hour()}:%02{date.Minute()} now.")

Für mich ist die zweite Version einfacher zu lesen und zu ändern und weniger fehleranfällig, da wir nicht auf die Reihenfolge angewiesen sind, in der wir die Variablen schreiben.

Wenn Sie die erste Version lesen, müssen Sie die Zeile mehrmals lesen, um zu überprüfen, ob sie korrekt ist, und Ihre Augen hin und her bewegen, um eine mentale Zuordnung zwischen der Position eines "%v" und der Stelle zu erstellen, an der Sie sie einfügen müssen Die Variable.

Ich habe Fehler in mehreren Anwendungen behoben (nicht in Go geschrieben, aber mit demselben Problem), bei denen die Datenbankabfragen mit vielen '?' anstelle von benannten Parametern ( SELECT * FROM ? WHERE ? = ? AND ? != false ... ) und weiteren Änderungen (sogar versehentlich während eines Git-Merges) wurde die Reihenfolge zweier Variablen vertauscht 😩

Für eine Sprache, deren Ziel es ist, die Wartbarkeit langfristig zu erleichtern, denke ich, dass es sich aus diesem Grund lohnt, eine Zeichenfolgeninterpolation in Betracht zu ziehen

In Bezug auf die Komplexität: Ich kenne die Interna des Go-Compilers nicht, also nehmen Sie meine Meinung mit einem Körnchen Salz, aber _Könnte es nicht einfach die zweite Version, die im obigen Beispiel gezeigt wird, in die erste übersetzen?_

Alle 67 Kommentare

Warum können wir nicht einfach ${...} verwenden? Swift hat bereits eine Syntax, JavaScript und Kotlin eine andere, C# noch eine, auch Perl... Warum noch eine Variante erfinden? Wäre es nicht besser, sich an die am besten lesbare und bereits existierende zu halten?

Warum können wir nicht einfach ${...} verwenden? Swift hat bereits eine Syntax, JavaScript und Kotlin eine andere, C# noch eine, auch Perl... Warum noch eine Variante erfinden? Wäre es nicht besser, sich an die am besten lesbare und bereits existierende zu halten?

Weil wir möglicherweise vorhandene Zeichenfolgen mit ${…} haben. Ich zum Beispiel.

Und \{ ist derzeit nicht erlaubt.

Dies scheint keinen großen Vorteil gegenüber dem Aufruf fmt.Sprintf zu haben.

Dies scheint keinen großen Vorteil gegenüber dem Aufruf fmt.Sprintf zu haben.

Ja, außer voller Typsicherheit, besserer Leistung und Benutzerfreundlichkeit nicht. Formatierung richtig gemacht für eine Sprache mit statischer Typisierung.

Soweit ich das beurteilen kann, ist dieser Vorschlag nicht typsicherer als die Verwendung fmt.Sprintf mit %v . Es ist im Wesentlichen identisch in Bezug auf die Typensicherheit. Ich stimme zu, dass eine sorgfältige Implementierung in vielen Fällen eine bessere Leistung erzielen könnte. Ich bin agnostisch in Bezug auf Benutzerfreundlichkeit.

Um dies zu implementieren, müssten wir in die Sprachspezifikation die genaue Formatierung schreiben, die für alle Typen verwendet werden soll. Wir müssten entscheiden und dokumentieren, wie ein Slice, ein Array, eine Map formatiert wird. Eine Schnittstelle. Ein Kanal. Dies wäre eine bedeutende Ergänzung der Sprachspezifikation.

Ich denke, es ist eine dieser Fragen, bei der beide Wege eine Existenzberechtigung haben und die Entscheidung nur den Entscheidungsträgern überlassen bleibt. In Go ist die Entscheidung bereits getroffen, vor ziemlich langer Zeit, und es funktioniert, und es ist idiomatisch. Das ist fmt.Sprintf mit %v .

Historisch gesehen gibt es Sprachen, in denen die Interpolation innerhalb einer Zeichenfolge von Anfang an vorhanden war. Insbesondere ist es Perl. Das ist einer der Gründe, warum Perl so beliebt wurde, weil es im Vergleich zu sprintf() in C et al. superpraktisch war. Und das %v war damals noch nicht erfunden. Und dann gibt es Sprachen, in denen die Interpolation zwar vorhanden war, aber syntaktisch unbequem war, denken Sie an "text" + v1 + "text" . JavaScript führte dann Backtick-quotierte Literale ein, die mehrzeilig sind und die Interpolation beliebiger Ausdrücke innerhalb ${...} unterstützen, was eine enorme Verbesserung im Vergleich zu "text" + v1 + "text" . Das Go hat auch mehrzeilige Backtick-Literale, aber ohne ${...} . Wer wen kopiert hat, weiß ich nicht.

Ich stimme @ianlancetaylor nicht zu, dass die Unterstützung dafür erhebliche Anstrengungen erfordern wird. Tatsächlich macht fmt.Sprintf mit %v genau das, nicht wahr? Es sieht für mich wie ein anderer Syntax-Wrapper für genau dasselbe unter der Haube aus. Habe ich recht?

Aber __ich stimme @ianlancetaylor zu, dass die Verwendung fmt.Sprintf mit %v genauso praktisch ist. Es ist auch auf dem Bildschirm genau gleich lang und, was meiner Meinung nach sehr wichtig ist, bereits idiomatisch für Go. Es macht Go Go Go. Wenn wir jedes andere Feature aus jeder anderen Sprache kopieren und implementieren, dann ist es nicht mehr Go, sondern jede andere Sprache.

Es gibt noch eine Sache. Aus meiner langjährigen Erfahrung mit Perl, wo String-Interpolation von Anfang an vorhanden war, kann ich sagen, dass es nicht so perfekt ist. Es gibt ein Problem damit. Für einfache und triviale Programme und wahrscheinlich für 90 % aller Programme funktioniert die Zeichenfolgeninterpolation von Variablen einwandfrei. Aber hin und wieder bekommt man var=1.99999999999 und möchte es als 1.99 ausgeben, und das __kann__ nicht__ mit Standard-String-Interpolation machen. Sie müssen entweder vorher etwas umwandeln oder ... Schauen Sie in die Dokumentation und lernen Sie die lange vergessene sprintf() -Syntax neu. Hier __ist__ das Problem - die Verwendung von String-Interpolation lässt Sie vergessen, wie man sprintf()-ähnliche Syntax verwendet, und wahrscheinlich existiert sie überhaupt. Und dann, wenn es nötig ist, verbringen Sie zu viel Zeit und Mühe mit den einfachsten Dingen. Ich spreche im Perl-Kontext, und es war eine Entscheidung von Sprachdesignern, dies zu tun.

Aber in Go wurde eine andere Entscheidung getroffen. Das fmt.Sprintf mit %v ist bereits da, es ist __so praktisch, so kurz__ wie interpolierte Strings, und es ist __idiomatisch__, da es überall in der Dokumentation, in Beispielen, steht. Und es leidet nicht unter dem Problem, irgendwann zu vergessen, wie man 1,99999999999 als 1,99 druckt.

Die Einführung der vorgeschlagenen Syntax wird Go ein wenig mehr wie Swift und/oder mehr wie JavaScript machen, und einige mögen das vielleicht. Aber ich denke, diese spezielle Syntax wird es nicht besser machen, wenn nicht sogar ein bisschen schlechter.

Ich denke, dass die bestehende Art, Dinge zu drucken, so bleiben sollte, wie sie ist. Und wenn jemand mehr braucht – dann gibt es dafür Vorlagen.

Wenn ein Teil des Arguments hier die Sicherheit zur Kompilierzeit ist, stimme ich nicht zu, dass dies ein zwingendes Argument ist; go vet und damit go test melden seit einiger Zeit falsche Verwendungen von fmt.Sprintf .

Es ist auch möglich, die Leistung heute über go generate zu optimieren, wenn Sie dies wirklich möchten. Es ist ein Kompromiss, der sich meistens nicht lohnt. Ich habe das Gefühl, dass dasselbe für die starke Erweiterung der Spezifikation gilt. Der Kompromiss lohnt sich im Allgemeinen nicht.

Das Zulassen von Funktionsaufrufen innerhalb interpolierter Zeichenfolgen wäre unglücklich - zu leicht zu übersehen.
Sie nicht zuzulassen ist ein weiterer unnötiger Sonderfall.

Wenn ein Teil des Arguments hier die Sicherheit zur Kompilierzeit ist, stimme ich nicht zu, dass dies ein zwingendes Argument ist; go vet und damit auch go test weisen seit einiger Zeit auf falsche Verwendungen von fmt.Sprintf hin. Es ist auch möglich, die Leistung heute über go generate zu optimieren, wenn Sie dies wirklich möchten. Es ist ein Kompromiss, der sich meistens nicht lohnt. Ich habe das Gefühl, dass dasselbe für die starke Erweiterung der Spezifikation gilt. Der Kompromiss lohnt sich im Allgemeinen nicht.

Die Typsicherheit sollte jedoch vom Compiler gewährleistet werden, nicht von Tools. Das ist Semantik; Es ist, als würde man sagen, dass es ein Tool geben sollte, mit dem Sie überprüfen können, wo Sie vergessen haben, auf Null zu prüfen, anstatt optionale oder explizit deklarierte Nullable-Werte zu haben.

Abgesehen davon ist dies nur mit abhängigen Typen sicher. String-Interpolation ist einfach mehr syntaktischer Zucker für das gleiche Zeug wie fmt.Sprintf , und obwohl ich für etwas guten Zucker bin, scheint die ganze Go-Community das nicht zu sein.

Oder vielleicht so etwas wie das Ändern der Sprache, um einfach besser mit fmt.Printf & Freunden zu funktionieren.

Wenn fmt so etwas wie %(foo)v oder %(bar)q unterstützt, dann sagen Sie, dass wenn ein String-Literal mit %(<ident>) in einem Aufruf einer variadischen Funktion/Methode verwendet wird, dann alle referenziert werden Symbole werden automatisch an die variadische Liste angehängt.

zB dieser Code:

name := "foo"
age := 123
fmt.Printf("The gopher %(name)v is %(age)2.1f days old.")

würde wirklich kompilieren zu:

name := "foo"
age := 123
fmt.Printf("The gopher %(name)v is %(age)2.1f days old.", name, age)

Und fmt könnte einfach die unnötigen (name) und (age) Bits überspringen.

Das ist jedoch ein ziemlich spezieller Sprachwechsel.

Für mich ist Typprüfung als Grund, String-Interpolation in die Sprache aufzunehmen, nicht so überzeugend. Es gibt einen wichtigeren Grund, der nicht von der Reihenfolge abhängt, in der Sie die Variablen in fmt.Printf schreiben.

Nehmen wir eines der Beispiele aus der Vorschlagsbeschreibung und schreiben es in Go mit und ohne String-Interpolation:

  • Ohne String-Interpolation (aktuelles Go)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
  • Äquivalente Funktionalität mit String-Interpolation (und Mischen des @bradfitz- Kommentars, der benötigt wird, um die Formatierungsoptionen auszudrücken)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %{name}! Today is %{date.Weekday()}, it's %02{date.Hour()}:%02{date.Minute()} now.")

Für mich ist die zweite Version einfacher zu lesen und zu ändern und weniger fehleranfällig, da wir nicht auf die Reihenfolge angewiesen sind, in der wir die Variablen schreiben.

Wenn Sie die erste Version lesen, müssen Sie die Zeile mehrmals lesen, um zu überprüfen, ob sie korrekt ist, und Ihre Augen hin und her bewegen, um eine mentale Zuordnung zwischen der Position eines "%v" und der Stelle zu erstellen, an der Sie sie einfügen müssen Die Variable.

Ich habe Fehler in mehreren Anwendungen behoben (nicht in Go geschrieben, aber mit demselben Problem), bei denen die Datenbankabfragen mit vielen '?' anstelle von benannten Parametern ( SELECT * FROM ? WHERE ? = ? AND ? != false ... ) und weiteren Änderungen (sogar versehentlich während eines Git-Merges) wurde die Reihenfolge zweier Variablen vertauscht 😩

Für eine Sprache, deren Ziel es ist, die Wartbarkeit langfristig zu erleichtern, denke ich, dass es sich aus diesem Grund lohnt, eine Zeichenfolgeninterpolation in Betracht zu ziehen

In Bezug auf die Komplexität: Ich kenne die Interna des Go-Compilers nicht, also nehmen Sie meine Meinung mit einem Körnchen Salz, aber _Könnte es nicht einfach die zweite Version, die im obigen Beispiel gezeigt wird, in die erste übersetzen?_

@ianlancetaylor wies darauf hin, dass meine obige Skizze (https://github.com/golang/go/issues/34174#issuecomment-532416737) nicht streng abwärtskompatibel ist, da es möglicherweise seltene Programme gibt, bei denen dies ihr Verhalten ändern würde.

Eine abwärtskompatible Variante wäre das Hinzufügen eines neuen Typs von "formatiertem Zeichenfolgenliteral", dem beispielsweise ein f vorangestellt wird:

zB dieser Code:

name := "foo"
age := 123
fmt.Printf(f"The gopher %(name)v is %(age)2.1f days old.")

würde wirklich kompilieren zu:

name := "foo"
age := 123
fmt.Printf(f"The gopher %(name)v is %(age)2.1f days old.", name, age)

Aber dann würde das doppelte f (eins in Printf gefolgt von f vor dem neuen String-Literal) stottern.

Ich verstehe auch das Innenleben des Compilers nicht, daher gehe ich (vielleicht dummerweise) auch davon aus, dass dies im Compiler implementiert werden könnte, sodass so etwas wie
fmt.printf("Hello %s{name}. You are %d{age}")

zu seiner äquivalenten aktuellen Formulierung kompilieren würde.

Die Zeichenfolgeninterpolation hat den offensichtlichen Vorteil einer besseren Lesbarkeit (eine zentrale Designentscheidung von Go) und lässt sich auch besser skalieren, wenn die Zeichenfolgen, mit denen man sich befasst, länger und komplizierter werden (eine weitere zentrale Designentscheidung von Go). Bitte beachten Sie auch, dass die Verwendung von {age} den String-Kontext ergibt, den er sonst nicht hätte, wenn Sie den String nur oberflächlich lesen würden (und natürlich den angegebenen Typ ignorieren), der String hätte mit "You are tall" enden können. „Sie sind [am Standort XXX]“, „Sie arbeiten zu hart“, und wenn Sie nicht die mentale Energie aufbringen, die Formatmethode jeder Interpolationsinstanz zuzuordnen, ist nicht sofort ersichtlich, was dort hingehört. Durch das Entfernen dieser (zugegebenermaßen kleinen) mentalen Hürde kann sich der Programmierer auf die Logik statt auf den Code konzentrieren.

Der Compiler implementiert die Sprachspezifikation. Die Sprachspezifikation sagt derzeit überhaupt nichts über das fmt-Paket aus. Es muss nicht. Sie können große Go-Programme schreiben, die das fmt-Paket überhaupt nicht verwenden. Das Hinzufügen der fmt-Paketdokumentation zur Sprachspezifikation würde diese merklich vergrößern, was eine andere Art zu sagen ist, dass die Sprache dadurch viel komplexer wird.

Das macht die Annahme dieses Vorschlags nicht unmöglich, aber es entstehen große Kosten, und wir brauchen einen großen Nutzen, der diese Kosten aufwiegt.

Oder wir brauchen eine Möglichkeit, die String-Interpolation zu diskutieren, ohne das fmt-Paket einzubeziehen. Dies ist ziemlich klar für Werte vom Typ String oder sogar vom Typ []byte , aber viel weniger klar für Werte anderer Typen.

Ich bin nicht für diesen Vorschlag, teilweise aufgrund dessen, was @IanLanceTaylor oben gesagt hat, und teilweise, weil beim Versuch, komplexe Ausdrücke mit Formatierungsoptionen zu interpolieren, jeder Lesbarkeitsvorteil tendenziell aus dem Fenster geht.

Außerdem wird manchmal vergessen, dass die Möglichkeit, variadische Argumente in die Funktionsfamilie fmt.Print (und Println ) aufzunehmen, bereits eine Form der Interpolation ermöglicht. Wir können einige der zuvor zitierten Beispiele leicht mit dem folgenden Code reproduzieren, der meiner Meinung nach genauso lesbar ist:

multiplier := 3
message := fmt.Sprint(multiplier, " times 2.5 is ", float64(multiplier) * 2.5)

age := 21
fmt.Println("My age is:", age)

name := "Mark"
date := time.Now()
fmt.Print("Hello, ", name, "! Today is ", date.Weekday(), ", it's ", date.String()[11:16], " now.\n")

name = "foo"
days := 12.312
fmt.Print("The gopher ", name, " is ", fmt.Sprintf("%2.1f", days), " days old\n.")

Ein weiterer Grund, es noch hinzuzufügen und in der Sprache zu __haben__: https://github.com/golang/go/issues/34403#issuecomment -542560071

Wir finden die Kommentare von @alanfo in https://github.com/golang/go/issues/34174#issuecomment -540995458 überzeugend: Sie können fmt.Sprint verwenden, um eine einfache Art von String-Interpolation durchzuführen. Die Syntax ist vielleicht weniger bequem, aber jeder Ansatz würde in jedem Fall einen speziellen Marker für zu interpolierende Variablen erfordern. Und dies erlaubt, wie erwähnt, eine willkürliche Formatierung der zu interpolierenden Werte.

Wie oben erwähnt, gibt es eine Möglichkeit, dies ungefähr zu tun, die sogar die Formatierung der einzelnen Variablen ermöglicht. Daher ist dies ein wahrscheinlicher Rückgang . Vier Wochen lang für abschließende Kommentare offen lassen.

Ich werde regelmäßig mit dem Bau von Textblöcken mit weit über 50 einzufügenden Variablen konfrontiert. In einigen Fällen über 70. Dies wäre mit Pythons F-Strings (ähnlich wie oben erwähntes C#) einfach zu warten. Aber ich handhabe das aus mehreren Gründen in Go statt in Python. Die anfängliche Einrichtung von fmt.Sprintf zur Verwaltung dieser Blöcke ist ... ok. Aber Gott bewahre, dass ich einen Fehler beheben oder den Text auf irgendeine Weise ändern muss, die das Verschieben oder Löschen %anything -Markierungen und ihren positionsbezogenen Variablen beinhaltet. Und das manuelle Erstellen von Karten für den Übergang zu template oder das Einrichten von os.Expand ist auch keine gute Option. Ich nehme die Geschwindigkeit (des Setups) und die einfache Wartbarkeit von F-Saiten an jedem Tag der Woche über fmt.Sprintf . Und nein, fmt.Sprint wären nicht sehr vorteilhaft. In diesem Fall einfacher einzurichten als fmt.Sprintf . Aber es verliert visuell viel von seiner Bedeutung, weil Sie in Saiten hinein- und herausspringen. "My {age} is not {quality} in this discussion" springt nicht wie "My ", age, " is not ", quality, " in this discussion" in und aus Strings. Vor allem im Laufe der vielen zig Referenzen. Das Verschieben von Text und Referenzen ist einfach Kopieren und Einfügen mit F-Strings. Löschungen sind nur auswählen und löschen. Weil du immer innerhalb der Saite bist. Dies ist bei Verwendung fmt.Sprint nicht der Fall. Es ist sehr einfach, versehentlich (oder notwendigerweise) Nicht-String-Kommas oder doppelte Anführungszeichen-String-Endungen auszuwählen und sie zu verschieben, um die Formatierung zu brechen und Bearbeitungen zu erfordern, um sie wieder an ihren Platz zu bringen_. fmt.Sprint und fmt.Sprintf ist in diesen Fällen viel zeitaufwändiger und fehleranfälliger als alles, was F-Strings ähnelt.

Das klingt wie eine ziemlich schreckliche Aufgabe, wie auch immer Sie es tun!

Wenn ich mit so etwas konfrontiert wäre, würde ich anfangs sicherlich an text/template denken oder, wenn es zu umständlich wäre, meine Variablen in eine Struktur oder Map zu bringen, würde ich wahrscheinlich fmt.Sprint bevorzugen fmt.Sprintf , aber ordnen Sie den Code folgendermaßen an:

s := fmt.Sprint(
    text1, var1,     // comment 1
    text2, var2,     // comment 2
    ....,
    text70, var70,   // comment 70
    text71,
)

Obwohl dies viel Platz auf dem Bildschirm einnehmen würde, wäre es relativ einfach, Dinge zu ändern, zu löschen oder zu verschieben, ohne zu viel Risiko ein Fehler zu machen.

Es gibt einige Go-String-Interpolationsbibliotheken, aber ohne Sprachfunktionen wie Vereinigungstypen oder Generika sind sie nicht so flexibel oder flüssig wie in anderen Sprachen:

package main

import (
    "fmt"

    "github.com/imkira/go-interpol"
)

func main() {
    m := map[string]string{
        "foo": "Hello",
        "bar": "World",
    }
    str, err := interpol.WithMap("{foo} {bar}!!!", m)
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Println(str)
}

Alternativ go.uber.org/yarpc/internal/interpolate

Noch einmal, das Erstellen einer Karte, nur damit ich template/text , eine Bibliothek eines Drittanbieters oder ein minimales System für os.Expand verwenden kann, ist großartig, wenn Sie nicht 50 oder mehr Schlüssel haben, die Sie für Variablen erstellen müssen die für den Rest des betreffenden Codes bereits vorhanden sind. Und das einzige wirkliche Argument gegen diese Funktion ist "Sie könnten vergessen, wie man die %-Formatierung richtig verwendet" und müssen möglicherweise zwei zusätzliche Minuten mit Google verbringen, um die Formatierung für die eine Instanz nachzuschlagen, für die Sie unbedingt die Effizienz, Spezifität oder was auch immer benötigen fmt.Sprintf ? Auf der anderen Seite brauchen Sie vielleicht fast immer die Effizienz, Spezifität oder was auch immer von fmt.Sprintf und vergessen die Formatierung überhaupt nicht, weil Sie sie immer verwenden. Ich sehe das Problem nicht.

Das andere Argument ist, dass es die Sprache komplexer macht? Ja tut es. Technisch erhöht jede Ergänzung der Sprache ihre Komplexität. Und ich bin dafür, die Sprache nicht aus trivialen Gründen komplexer zu machen. Ich würde _WIRKLICH_ gerne die Verwendung von in für all die Arten sehen, wie es in Python verwendet wird. Aber ich würde mich damit begnügen, := range einfach durch $#$ in $#$ zu ersetzen. Ich habe Python im letzten Jahr kaum berührt und nur zwei Jahre zuvor verwendet. Aber in neun von zehn Fällen tippe ich zuerst in ein und behebe es dann mit := range . Aber darum werde ich mich nicht streiten. := range ist ausreichend und offensichtlich, um über Code nachzudenken, den Sie zuvor geschrieben haben. Aber F-Saiten und ihre Art ist etwas ganz anderes. Das ist Kernfunktionalität, Benutzerfreundlichkeit und Wartbarkeit. Es sollte unbedingt Teil der Go-Spezifikation sein. Wenn es eine Möglichkeit gäbe, dies als Bibliothek eines Drittanbieters zu tun, würde ich die Bibliothek erstellen und damit fertig sein. Aber meines Wissens ist es nicht einmal ohne zusätzliche Erweiterung der Go-Sprache machbar. Also zumindest denke ich, dass eine Erweiterung der Sprache vorhanden sein sollte.

@runeimp Ich möchte klarstellen, dass dein Vorschlag, dass wir mit der Unterstützung "My {age} is not {quality} in this discussion" beginnen könnten, nicht funktioniert. Das ist heute ein gültiger Go-String, und wenn wir damit rechnen würden, age und quality zu interpolieren, würde das bestehende Go-Programme kaputt machen. Es müsste eher so etwas wie "My \{age} is not \{quality} in this discussion" sein. Und die Probleme beim Formatieren von Nicht-String-Werten bleiben bestehen.

Ich schätze das @ianlancetaylor. Und um es klarzustellen: Python-F-Strings sind f"My {age} is not {quality} in this discussion" oder F'My {age} is not {quality} in this discussion' oder eine beliebige Kombination aus jedem Fall f und einem einfachen oder doppelten Anführungszeichen. Wie bereits erwähnt, sehr ähnlich wie C # es zu tun scheint. Auch wenn ich den Python-f-String- oder C#-Stil bevorzugen würde, werde ich absolut _ALLES_ akzeptieren, das eine ähnliche Verwendung ermöglicht. InterPolationACTIVATE"My @$@age###^&%$ is not @$@quality###^&%$ in this discussion"INTERpolationDEACTVATEandNullify wäre akzeptabel. Lahm, aber akzeptabel. Und ich scherze nicht einmal. Ich würde diese lahme Entschuldigung für die String-Interpolation über fmt.Sprintf an jedem Tag der Woche nehmen. Allein die Wartungsvorteile würden es rechtfertigen.

Ich verstehe, dass es Ihrem speziellen Fall helfen würde.

In https://blog.golang.org/go2-here-we-come schrieb @griesemer , dass sich die Sprache ändern sollte

  1. ein wichtiges Thema für viele Menschen ansprechen,
  2. haben nur minimale Auswirkungen auf alle anderen, und
  3. kommen mit einer klaren und gut verständlichen Lösung.

Ist das für viele Menschen ein wichtiges Thema?

Ich habe keine Ahnung, aber ich würde es erwarten. Ich denke, es ist ein Feature, das für niemanden wichtig ist, der jemals nur mit 5 Referenzen oder weniger auf einer kurzen Saite zurechtkommen musste. Oder musste es aufgrund anderer Spezifikationsanforderungen sowieso immer mit Vorlagen verwalten. Aber für diejenigen von uns, die es benutzen. Es wird ziemlich schmerzlich vermisst, wenn es nicht vorhanden ist. Und für diejenigen, die noch nie die Möglichkeit hatten (vor Go nur C, C++ usw. verwendet haben), kann es sehr schwierig sein, die Vorteile auf rein theoretischer Ebene zu verstehen, da die Beispiele in ihrer Erfahrung wahrscheinlich schnell vergessen werden oder nur als die schlimmsten Projekte in Erinnerung bleiben, mit denen sie sich auseinandersetzen mussten, um die Schmerzpunkte zu vergessen. Ich bezweifle, dass Sie jemals eine Mehrheitsantwort zu diesem Thema sehen würden, es sei denn, die meisten Go-Entwickler kamen aus Sprachen, die tatsächlich die Interpolation lokaler Variablenzeichenfolgen unterstützen.

Es ist ein Problem, auf das ich immer gestoßen bin, seit ich vor über 20 Jahren als professioneller Webentwickler angefangen habe. In den meisten dieser Fälle war Templating für Front-End und Back-End üblich. Am Ende dieses Teils meiner Karriere arbeitete ich schließlich in Ruby on Rails und verliebte mich sofort in Rubys "My #{age} is not #{quality} in this discussion" String-Interpolation. Obwohl mir die Pfund-geschweifte Klammer-Syntax anfangs sehr seltsam vorkam. Als ich zum Integration Engineering wechselte, verwendete ich hauptsächlich Python 3 und war viel glücklicher mit dem neuen str.format() -System anstelle der alten %-formatierten Zeichenfolgen. Damit würden Sie so etwas wie "My {} is not {} in this discussion".format(age, quality) machen. Die Verwendung agnostischer Referenzen zumindest des Typs spielte also für 90 % der Anwendungsfälle keine Rolle. Was einfach einfacher zu verstehen ist und sich nur um den Index kümmert. Auch benannte Referenzen wie "My {age} is not {quality} in this discussion".format(age=my_age_var, quality=my_quality_var) . Wenn jetzt dieselbe Variable 30 Mal referenziert wird, müssen Sie sie nur einmal in den Parametern angeben, und es ist einfach, den Überblick zu behalten, zu kopieren und einzufügen oder zu löschen. Benannte Parameter (als Eingabe) sind ein weiteres Feature von Python, das ich in Go vermisse. Aber wenn es sein muss, kann ich auch ohne leben. Aber ich habe mich wieder in F-Strings (eingeführt in Python 3.6) verliebt, als ich es gesehen habe. String-Interpolation hat mir schon immer das Leben erleichtert.

@runeimp Könnten Sie ein Beispiel für eine dieser über 50 Variablen-String-Interpolationen posten (in jeder Sprache, in der Sie sie haben)? Ich denke, es könnte der Diskussion helfen, ein tatsächliches Beispiel des fraglichen Codes zu haben.

OK, dies ist kein endgültiger Code, ist ein einfaches Beispiel, hat nur 50 Referenzen und die Variablennamen wurden geändert, um Unschuldige zu schützen.

Gehen Sie zu fmt.Sprintf

func (dt DataType) String() string {
    MMMCodeTail := " "
    if len(pn.MMMCode) > 0 {
        MMMCodeTail = "\n\t"
    }

    MMMVCTail := " "
    if len(pn.MMMVoipCommunication) > 0 {
        MMMVCTail = "\n\t"
    }

    MMMCCTail := " "
    if len(pn.MMMCombatConditions) > 0 {
        MMMCCTail = "\n\t"
    }

    MMMSRTail := " "
    if len(pn.MMMSecurityReporting) > 0 {
        MMMSRTail = "\n\t"
    }

    MMMLKTail := " "
    if len(pn.MMMLanguagesKnown) > 0 {
        MMMLKTail = "\n\t"
    }

    return fmt.Sprintf(`ThingID=%6d, ThingType=%q, PersonID=%d, PersonDisplayName=%q, PersonRoomNumber=%q,
    DateOfBirth=%s, Gender=%q, LastViewedBy=%q, LastViewDate=%s,
    SaleCodePrior=%q, SpecialCode=%q, Factory=%q,
    Giver=%s, Manager=%q, ServiceDate=%s, SessionStart=%s, SessionEnd=%s, SessionDuration=%d,
    HumanNature=%q, VRCatalog=%v, AdditionTime=%d, MeteorMagicMuscle=%v,
    VRQuest=%q, SelfCare=%v, BypassTutorial=%q, MultipleViewsSameday=%v,
    MMMCode=%q,%sMMMVoipCommunication=%q,%sMMMCombatConditions=%q,%sMMMSecurityReporting=%q,%sMMMLanguagesKnown=%q,%sMMMDescription=%q,
    SaleCodeLatest=%q, HonoraryCode=%q, LegalCode=%q, CharacterDebuffs=%q,
    MentalDebuffs=%q, PhysicalDebuffs=%q,
    CharacterChallenges=%q,
    CharacterChallengesOther=%q,
    CharacterStresses=%q,
    RelationshipGoals=%q, RelationshipGoalsOther=%q,
    RelationshipLobsters=%q,
    RelationshipLobstersOther=%q,
    RelationshipLobsterGunslingerDoublePlus=%q,
    RelationshipLobsterGunslingerPlus=%q,
    RelationshipLobsterGunslingerGains=%q,
    PersonAcceptsRecognition=%q,
    PersonAcceptsRecognitionGunslinger=%q,
    BenefitsFromChocolate=%v, DinnerForLovelyWaterfall=%v, ModDinners=%q, ModDinnersOther=%q,
    FlexibleHaystackList=%q, FlexibleHaystackOther=%q,
    ModDiscorseSummary=%q,
    MentallySignedBy=%q, Overlord=%q, PersonID=%d,
    FactoryID=%q, DeliveryDate=%s, ManagerID=%q, ThingReopened=%v`,
        dt.ThingID,
        dt.ThingType,
        dt.PersonID,
        dt.PersonDisplayName,
        // dt.PersonFirstName,
        // dt.PersonLastName,
        dt.PersonRoomNumber,
        dt.DateOfBirth,
        dt.Gender,
        dt.LastViewedBy,
        dt.LastViewDate,
        dt.SaleCodePrior,
        dt.SpecialCode,
        dt.Factory,
        dt.Giver,
        dt.Manager,
        dt.ServiceDate,
        dt.SessionStart,
        dt.SessionEnd,
        dt.SessionDuration,
        dt.HumanNature,
        dt.VRCatalog,
        dt.AdditionTime,
        dt.MeteorMagicMuscle,
        dt.VRQuest,
        dt.SelfCare,
        dt.BypassTutorial,
        dt.MultipleViewsSameday,
        dt.MMMCode, MMMCodeTail,
        dt.MMMVoipCommunication, MMMVCTail,
        dt.MMMCombatConditions, MMMCCTail,
        dt.MMMSecurityReporting, MMMSRTail,
        dt.MMMLanguagesKnown, MMMLKTail,
        dt.MMMDescription,
        dt.SaleCodeLatest,
        dt.HonoraryCode,
        dt.LegalCode,
        dt.CharacterDebuffs,
        dt.MentalDebuffs,
        dt.PhysicalDebuffs,
        dt.CharacterChallenges,
        dt.CharacterChallengesOther,
        dt.CharacterStresses,
        dt.RelationshipGoals,
        dt.RelationshipGoalsOther,
        dt.RelationshipLobsters,
        dt.RelationshipLobstersOther,
        dt.RelationshipLobsterGunslingerDoublePlus,
        dt.RelationshipLobsterGunslingerPlus,
        dt.RelationshipLobsterGunslingerGains,
        dt.PersonAcceptsRecognition,
        dt.PersonAcceptsRecognitionGunslinger,
        dt.BenefitsFromChocolate,
        dt.DinnerForLovelyWaterfall,
        dt.ModDinners,
        dt.ModDinnersOther,
        dt.FlexibleHaystackList,
        dt.FlexibleHaystackOther,
        dt.ModDiscorseSummary,
        dt.MentallySignedBy,
        dt.Overlord,
        dt.PersonID,
        dt.FactoryID,
        dt.DeliveryDate,
        dt.ManagerID,
        dt.ThingReopened,
    )
}

Ein mögliches Beispiel für F-Saiten

func (dt DataType) String() string {
    MMMCodeTail := " "
    if len(pn.MMMCode) > 0 {
        MMMCodeTail = "\n\t"
    }

    MMMVCTail := " "
    if len(pn.MMMVoipCommunication) > 0 {
        MMMVCTail = "\n\t"
    }

    MMMCCTail := " "
    if len(pn.MMMCombatConditions) > 0 {
        MMMCCTail = "\n\t"
    }

    MMMSRTail := " "
    if len(pn.MMMSecurityReporting) > 0 {
        MMMSRTail = "\n\t"
    }

    MMMLKTail := " "
    if len(pn.MMMLanguagesKnown) > 0 {
        MMMLKTail = "\n\t"
    }

    return fmt.Print(F`ThingID={dt.ThingID}, ThingType={dt.ThingType}, PersonID={dt.PersonID}, PersonDisplayName={dt.PersonDisplayName}, PersonRoomNumber={dt.PersonRoomNumber},
    DateOfBirth={dt.DateOfBirth}, Gender={dt.Gender}, LastViewedBy={dt.LastViewedBy}, LastViewDate={dt.LastViewDate},
    SaleCodePrior={dt.SaleCodePrior}, SpecialCode={dt.SpecialCode}, Factory={dt.Factory},
    Giver={dt.Giver}, Manager={dt.Manager}, ServiceDate={dt.ServiceDate}, SessionStart={dt.SessionStart}, SessionEnd={dt.SessionEnd}, SessionDuration={dt.SessionDuration},
    HumanNature={dt.HumanNature}, VRCatalog={dt.VRCatalog}, AdditionTime={dt.AdditionTime}, MeteorMagicMuscle={dt.MeteorMagicMuscle},
    VRQuest={dt.VRQuest}, SelfCare={dt.SelfCare}, BypassTutorial={dt.BypassTutorial}, MultipleViewsSameday={dt.MultipleViewsSameday},
    MMMCode={dt.MMMCode},{MMMCodeTail}MMMVoipCommunication={dt.MMMVoipCommunication},{MMMVCTail}MMMCombatConditions={dt.MMMCombatConditions},{MMMCCTail}MMMSecurityReporting={dt.MMMSecurityReporting},{MMMSRTail}MMMLanguagesKnown={dt.MMMLanguagesKnown},{MMMLKTail}MMMDescription={dt.MMMDescription},
    SaleCodeLatest={dt.SaleCodeLatest}, HonoraryCode={dt.HonoraryCode}, LegalCode={dt.LegalCode}, CharacterDebuffs={dt.CharacterDebuffs},
    MentalDebuffs={dt.MentalDebuffs}, PhysicalDebuffs={dt.PhysicalDebuffs},
    CharacterChallenges={dt.CharacterChallenges},
    CharacterChallengesOther={dt.CharacterChallengesOther},
    CharacterStresses={dt.CharacterStresses},
    RelationshipGoals={dt.RelationshipGoals}, RelationshipGoalsOther={dt.RelationshipGoalsOther},
    RelationshipLobsters={dt.RelationshipLobsters},
    RelationshipLobstersOther={dt.RelationshipLobstersOther},
    RelationshipLobsterGunslingerDoublePlus={dt.RelationshipLobsterGunslingerDoublePlus},
    RelationshipLobsterGunslingerPlus={dt.RelationshipLobsterGunslingerPlus},
    RelationshipLobsterGunslingerGains={dt.RelationshipLobsterGunslingerGains},
    PersonAcceptsRecognition={dt.PersonAcceptsRecognition},
    PersonAcceptsRecognitionGunslinger={dt.PersonAcceptsRecognitionGunslinger},
    BenefitsFromChocolate={dt.BenefitsFromChocolate}, DinnerForLovelyWaterfall={dt.DinnerForLovelyWaterfall}, ModDinners={dt.ModDinners}, ModDinnersOther={dt.ModDinnersOther},
    FlexibleHaystackList={dt.FlexibleHaystackList}, FlexibleHaystackOther={dt.FlexibleHaystackOther},
    ModDiscorseSummary={dt.ModDiscorseSummary},
    MentallySignedBy={dt.MentallySignedBy}, Overlord={dt.Overlord}, PersonID={dt.PersonID},
    FactoryID={dt.FactoryID}, DeliveryDate={dt.DeliveryDate}, ManagerID={dt.ManagerID}, ThingReopened={dt.ThingReopened}`,
    )
}

Welche der beiden möchten Sie in den nächsten Jahren jeden Monat pflegen und hinzufügen, aktualisieren, löschen?

Kann ich keine auswählen? Beides sieht für mich erschreckend aus.

Was ist mit Ihrem Fall, der mit fmt.Sprintf("%#v", dt) nicht erledigt werden kann? Das heißt, was ist die Anforderung bei der Bestellung? Genaue Formatierung (z. B. = vs. : für Trennzeichen, %q vs. %v , ...)? Unbedruckte Felder? Zeilenumbrüche?

Analysiert ein anderes Programm die Ausgabe oder ist dies für den menschlichen Gebrauch?

Was ist mit der Verwendung von Reflect?

v := reflect.ValueOf(dt)
t := v.Type()
for i := 0; i < t.NumField(); i++ {
    f := t.Field(i)
    s += fmt.Sprintf("%s=%v", f.Name, v.Field(i))
    if t.Field(i).Type.Kind() == reflect.String && v.Field(i).Len() > 0 {
        s += "\n\t"
    } else {
        s += " "
    }
}

Beide Optionen sind für dieses sehr spezifische Beispiel sinnvoll. Aber das wird nicht für etwas Komplexeres fliegen. Oder buchstäblich irgendetwas anderes. Zum Beispiel, wenn dieselbe Referenz viele Male in einer Zeichenfolge verwendet wird. Oder die Werte sind nicht Teil einer einzelnen Struktur oder Zuordnung, sondern werden im selben Bereich generiert. Dies ist nur ein verstümmeltes Beispiel. Leider kann ich keinen echten Code anzeigen. Aber schauen Sie sich wirklich nur eine Webseite mit Hunderten von Wörtern an. Öffnen Sie die Quellansicht und stellen Sie sich vor, dass mehr als 50 Wörter dynamisch sein müssen. Stellen Sie sich nun vor, dies ist nicht für eine Webseite, Sie benötigen aus keinem anderen Grund ein Vorlagensystem, aber um dieses Problem möglicherweise zu lösen, sind alle Variablen bereits im Geltungsbereich und werden in anderen Teilen des Codes und/oder an mehreren Stellen verwendet in diesem Codeblock, und dieser Text kann sich alle 3 bis 4 Wochen auf manchmal große und sehr bedeutsame Weise ändern. Dies kann Teil eines Systems sein, das _gelegentlich_ einige oder alle PDF-, CSV-, SQL INSERT-, E-Mail-, API-Aufrufe usw. generieren muss.

Ohne reale Beispiele ist es wirklich schwer zu sagen, ob dieser Vorschlag die richtige Lösung für das Problem ist. Vielleicht ist die String-Interpolation wirklich die Lösung, oder vielleicht stoßen wir auf das XY-Problem . Vielleicht ist die richtige Lösung bereits irgendwo in der Sprache enthalten, oder die Lösung ist eine Änderung an Paketreflektieren oder vielleicht Pakettext/Vorlage.

Erstens bezweifle ich, dass dies für viele Menschen ein wichtiges Thema ist. Dies ist die einzige ernsthafte Diskussion, an die ich mich erinnern kann, und die Emoji-Abstimmung deutet nicht auf überwältigende Unterstützung hin. Bibliotheken von Drittanbietern sind auch nicht gerade reichlich vorhanden.

Zweitens ist darauf hinzuweisen, dass sogar fmt.Printf (und die Familie) wiederholte Argumente ausführen können:

fmt.Printf("R%s T%[1]s T%[1]s was a canine movie star\n", "in")

Drittens glaube ich nicht, dass Leute, die String-Interpolation in anderen Sprachen verwendet haben, es unbedingt in Go sehen möchten. Ich habe es zuvor in mehreren Sprachen verwendet und es ist zwar in Ordnung, wenn Sie einfache Variablen interpolieren möchten, sobald Sie jedoch versuchen, komplexere Ausdrücke, Funktionsaufrufe, Escapezeichen usw. zu verwenden und all dem dann Formatierungsdetails hinzuzufügen (was unweigerlich einen Ansatz im 'printf'-Stil bedeutet) kann das Ganze schnell zu einem unlesbaren Durcheinander werden.

Schließlich scheint mir, dass, wenn Sie eine große Anzahl von Variablen zu interpolieren haben, text/template der beste Ansatz ist, und vielleicht sollten wir deshalb nach Möglichkeiten suchen, dies bequemer zu machen, wenn die Variablen vorhanden sind nicht Teil einer wohldefinierten Struktur, anstatt etwas von dieser Komplexität in die Sprache selbst zu integrieren.

Alternativ DSL bauen:

func (dt *DataType) String() string {
    var s strings.Builder
    _ = fields.Format(&s, 
        fields.PaddedInt("ThingID", 6, dt.ThingID),
        fields.String("ThingType", dt.ThingType),
        fields.String("PersonID", dt.PersonID),
        fields.Time("DateOfBirth", dt.DateOfBirth),
        ...
    )
    return s.String()
}

oder

func (dt *DataType) String() string {
    var s fields.Builder
    s.PaddedInt("ThingID", 6, dt.ThingID),
    s.String("ThingType", dt.ThingType),
    s.String("PersonID", dt.PersonID),
    s.Time("DateOfBirth", dt.DateOfBirth),
    return s.String()
}

Ich kann die Meinung von @ianlancetaylor sehr gut nachvollziehen. Wir wissen, dass wir wie immer mit fmt. Printf leben können.

Gleichzeitig kann ich der Meinung von @alanfo nur zustimmen.

einfacher zu lesen und zu ändern und weniger fehleranfällig

Es ist sehr wichtig für eine robuste Systemprogrammierung.

Ich denke, die Übernahme von Funktionen/Ideen aus anderen Sprachen ist überhaupt keine Schande, wenn es den Menschen viel wertvolle Zeit sparen kann. Tatsächlich leiden viele Leute unter String-Interpolationsproblemen.
Es gibt den Grund, warum andere moderne Sprachen die Zeichenfolgeninterpolation übernehmen.

Ich möchte nicht, dass Go nur eine etwas bessere Sprache bleibt als C.
Ich denke, Go v2 ist eine Chance, viel besser zu werden.

image

IMHO, wenn die Zeichenfolgeninterpolationsfunktion bereitgestellt wird, werden sich die meisten von uns dafür entscheiden, sie zu verwenden. Wir können es fühlen.

p.s.
Leider ist der Fall von @runeimp zu grenzwertig. Ich kann den Schmerz der Umstände spüren. Aber es verwischt die Vorschlagsideen. (Nichts für ungut)

@doortts nichts für ungut genommen. Ich schätze immer ehrliche Gespräche.

Ich wusste, dass das Posten von echtem Code dem Streit einen Strich durch die Rechnung machen könnte. Und ich wusste auch, dass das Posten einer Annäherung, die ein wenig Kreativität erfordert, um sie mental zu erweitern, die Diskussion wahrscheinlich nur verwirren würde. Also habe ich ein Risiko eingegangen, weil ich nicht erwarte, dass es jemand anderes tut. Aber das Teilen von echtem Code ist keine Option, und die Stunden, die es dauern würde, dies mit einem konkreten Beispiel schwarz auf weiß zu bringen, für das ich ansonsten keine Verwendung habe, sind etwas, für das ich im Moment einfach nicht die Energie habe. Ich weiß bereits, dass dieses Argument bergauf geht, aus den Gründen, die ich bereits zuvor genannt habe. Ich weiß, dass ich nicht einer von einem Dutzend Menschen bin, die den wahren Wert in dieser Funktion sehen. Aber die meisten davon sind mit ziemlicher Sicherheit nicht in der Nähe dieses Threads. Ich bin nur hier, weil ich zufällig einen Beitrag darüber auf Reddit gesehen habe. Ansonsten war mir dieser Prozess fast völlig unbekannt. Ich war ziemlich zufrieden damit, wie sich die Dinge mit Go entwickelt haben, ohne dass ich das Bedürfnis verspürte, mich in die Unterhaltung einzumischen. Aber ich wollte bei dieser Diskussion helfen, weil ich wusste, dass es einfach nicht viele Stimmen geben würde, die dafür kämpfen würden, einfach aufgrund der Menge an Trägheit, die zu überwinden ist, wenn eine Änderung der Sprache vorgeschlagen wird. Ich glaube nicht, dass ich jemals einen so starken Widerstand gegen Änderungen in einer Sprache gesehen habe. Und das macht es mächtig einschüchternd, etwas Neues zu unterstützen. Ich sage nicht, dass jede Sprache alle neuen Funktionsanfragen ohne Überprüfung akzeptieren sollte. Nur, dass der Grund, warum es auf der Support-Seite ruhig zu sein scheint, nicht immer darin liegt, dass der Wunsch nicht da ist. Und damit bitte ich alle, die an dieser Funktion interessiert sind, bitte _etwas_ zu posten. Irgendetwas , damit wir einige echte Zahlen dazu sehen können.

Ich denke, weil es Feiertage sind, gibt es im Allgemeinen weniger Aufmerksamkeit. Die Zeichenfolgeninterpolation ist aus Entwicklersicht äußerst nützlich und erwünscht (weshalb sie in so vielen anderen Sprachen vorhanden ist), aber es scheint, dass dieser Vorschlag an dieser Stelle für eine so große Änderung nicht ausgereift ist, was ich nicht glaube, wenn er abgelehnt wird bedeutet, dass es sich nicht lohnt, es in Zukunft erneut zu besuchen. Die kleinen inkrementellen Änderungen, die dies offensichtlich machen würden, sind noch nicht vorhanden.

Das dynamische Auflösen von Variablen nach Namen aus dem Bereich ist meiner Meinung nach derzeit nicht möglich (obwohl Yaegi Eval dies zu tun scheint?).

Ich denke, der Beitrag, auf den @runeimp verweist, ist mein Beitrag in r/golang https://www.reddit.com/r/golang/comments/d1199a/why_is_there_no_equivalent_to_f_strings_in_python/
Wo es ein bisschen mehr Diskussion gibt.

Ich wollte nur meinen Hut in den Ring werfen und sagen, dass die variable Interpolation für mich enorm hilfreich wäre, etwas wie fmt.sprintf aus der Python-Welt zu kommen, lässt Go ernsthaft klobig und schrecklich zu lesen/warten erscheinen.

Go ist meistens sehr elegant darin, wie es die Dinge, die es schätzt, sehr einfach und leistungsstark macht. Lesbarkeit und Wartbarkeit sind einige der Grundpfeiler seiner Philosophie, und es sollte wirklich nicht so schwierig sein, einen String zu lesen und zu wissen, was darin vor sich geht. Es gibt eine wirklich nicht triviale Menge an Overhead, die damit einhergeht, zu verstehen, was gedruckt wird, während man bedenkt, welche Variablen welchen Elementen in einer Liste am Ende dieser Zeichenfolge zugeordnet sind. Wir sollten diesen mentalen Overhead einfach nicht aufrechterhalten müssen.

Wenn Leute das Gefühl haben, dass fmt.sprintf für sie geeignet ist, kann es verwendet werden. Ich habe nicht das Gefühl, dass beide von Go als Sprache ablenken, da der Vorschlag zweifellos besser lesbar als die aktuelle Methode, intuitiv und einfacher zu warten ist. Dies sollte nicht durch Interpolationen von mehr als 50 Variablen gerechtfertigt werden müssen, es ist eine Verbesserung mit Interpolationen von nur einer Variablen.

Ist so etwas wie

fmt.sprintf("I am %<type name here>{age} years old.")

# or
fmt.sprintf("I am %T{age} years old")

wirklich eine schädliche Aussicht auf die Sprache? Ich bin froh, dass ich mich irre, aber ich sehe ehrlich gesagt nur Vorteile in diesem (oder so etwas ähnlichen) Vorschlag, mit Ausnahme der Abwärtsinkompatibilität, wo so etwas wie die Implementierung in einer neuen Hauptversion von Go sinnvoll ist

Könnte es in Betracht gezogen werden, diese Diskussion für einen weiteren Satz von 4 Wochen offen zu lassen, da ein beträchtlicher Teil der Community über Thanksgiving, Weihnachten und Neujahr im Urlaub war, als die Unterbrechung vorgeschlagen wurde?

Seit https://github.com/golang/go/issues/34174#issuecomment -558844640 hat es eine Menge weiterer Diskussionen gegeben, also nehme ich dies aus der Final-Comment-Periode zurück.

Ich neige dazu, @randall77 zuzustimmen , dass ich hier noch kein überzeugendes Beispiel gesehen habe. @runeimp , danke für das Posten des Beispielcodes, aber es scheint schwierig zu lesen und so oder so schwer zu ändern. Wie @egonelbre vorschlägt, scheint der erste Schritt darin zu bestehen, einen völlig anderen Ansatz zu finden, wenn wir dies wartbarer machen wollen.

@cyclingwithelephants fmt.sprintf("I am %T{age} years old") kommt hier nicht auf den Tisch. Die Sprache bietet keinen Mechanismus, den fmt.Sprintf verwenden könnte, um age aufzulösen. Go ist eine kompilierte Sprache, und lokale Variablennamen sind zur Ausführungszeit nicht verfügbar. Dies wäre schmackhafter, wenn wir einen Weg finden könnten, dies zum Laufen zu bringen, vielleicht in Anlehnung an den obigen Vorschlag von @bradfitz .

Vielen Dank an @ianlancetaylor für das Aufheben des Labels für den letzten Kommentar. 😀

Ich finde die Idee von @bradfitz großartig. Ich würde vermuten, dass es ohne einen lokalen Variablenkontext potenzielle Einschränkungen gibt, aber ich würde diese Einschränkungen gerne akzeptieren, wenn keine Zeichenfolgeninterpolation vorhanden ist. Obwohl ich wahnsinnigen Respekt vor den Aktualisierungen der Prozentformatierung in Go habe (ich liebe die Hinzufügungen von %q , %v und %#v ), ist dieses Paradigma uralt. Nur weil es ehrwürdig ist, heißt das nicht, dass es der beste Weg ist, Dinge zu tun. So wie Go beim Kompilieren vorgeht, und insbesondere Cross-Compiling ist _WEG_ besser als mit C oder C++. Nun, verbirgt Go nur all die fiesen Compiler-Optionen mit vernünftigen Standardeinstellungen und es ist genauso hässlich unter der Haube. Ich weiß es nicht genau, aber ich glaube, das ist der Fall. Und das ist in Ordnung. Das ist für mich völlig akzeptabel. Es ist mir egal, welches dunkle Ritual der Compiler durchführt, damit das Feature funktioniert. Ich weiß nur, dass die Funktion das Leben einfacher macht. Und hat mein Leben als Entwickler in jeder Sprache, die ich verwendet habe und die dies unterstützt, erleichtert. Und ist immer ein Schmerz in Sprachen, die es nicht unterstützen. Es ist wesentlich einfacher, sich daran zu erinnern, wie man die über 30 Sonderzeichen in der Prozentformatierung für 98 % der von mir benötigten Zeichenfolgenformatierung verwendet. Und zu sagen, %v anstelle von "dem richtigen Prozentformat" zu verwenden, ist nicht die gleiche Benutzerfreundlichkeit für die Erstellung und nicht einmal die gleiche Wartungsfreundlichkeit.

Jetzt, da ich etwas mehr Zeit habe, werde ich an einem Beispiel arbeiten und sehen, ob ich einige Artikel finden kann, die etwas aufschlussreicher sind als ich, um die erheblichen Vorteile für die Arbeitseffizienz für diejenigen von uns zu veranschaulichen, die sich mit menschlichen Schnittstellen befassen , Dokument- und Codegenerierung und String-Manipulation regelmäßig.

Hier ist ein Gedanke, der zu etwas Umsetzbarem führen kann. Obwohl ich nicht weiß, dass es eine gute Idee ist.

Fügen Sie einen neuen Stringtyp m"str" (und vielleicht das gleiche mit einem Rohstring). Diese neue Art von Zeichenfolgenliteral wird zu map[string]interface{} ausgewertet. Wenn Sie den leeren String in der Map nachschlagen, erhalten Sie das String-Literal selbst. Das Zeichenfolgenliteral kann Ausdrücke in geschweiften Klammern enthalten. Ein Ausdruck in geschweiften Klammern wird ausgewertet, als wäre er nicht im Zeichenfolgenliteral enthalten, und der Wert wird in der Zuordnung gespeichert, wobei der Schlüssel die Teilzeichenfolge ist, die in den geschweiften Klammern erscheint.

Zum Beispiel:

    i := 1
    m := m"twice i is {i * 2}"
    fmt.Println(m[""])
    fmt.Println(m["i * 2"])

Dies wird gedruckt

twice i is {i * 2}
2

Innerhalb des Zeichenfolgenliterals können geschweiften Klammern mit einem umgekehrten Schrägstrich maskiert werden, um eine einfache geschweifte Klammer anzuzeigen. Eine nicht in Anführungszeichen gesetzte, nicht übereinstimmende geschweifte Klammer ist ein Kompilierungsfehler. Es ist auch ein Kompilierungsfehler, wenn der Ausdruck innerhalb der geschweiften Klammern nicht kompiliert werden kann. Der Ausdruck muss genau einen Wert ergeben, ist aber ansonsten nicht eingeschränkt. Derselbe geklammerte String kann mehrmals im String-Literal vorkommen; sie wird so oft ausgewertet, wie sie erscheint, aber nur eine der Auswertungen wird in der Karte gespeichert (weil sie alle den gleichen Schlüssel haben). Welche genau gespeichert wird, ist nicht angegeben (dies ist wichtig, wenn der Ausdruck ein Funktionsaufruf ist).

An sich ist dieser Mechanismus eigenartig, aber nutzlos. Sein Vorteil ist, dass es klar spezifiziert werden kann und wohl keine übermäßigen Ergänzungen der Sprache erfordert.

Die Nutzung kommt mit zusätzlichen Funktionen. Die neue Funktion fmt.Printfm funktioniert genauso wie fmt.Printf , aber das erste Argument wird nicht string sein, sondern map[string]interface{} . Der "" in der Zuordnung ist eine Formatzeichenfolge. Der Format-String unterstützt neben den üblichen % -Dingen einen neuen {str} -Modifikator. Die Verwendung dieses Modifikators bedeutet, dass anstelle der Verwendung eines Arguments für den Wert str in der Map nachgeschlagen wird und dieser Wert verwendet wird.

Zum Beispiel:

    hi := "hi"
    fmt.Printfm(m"%20{hi}s")

gibt die Zeichenfolge hi aus, die an 20 Leerzeichen weitergegeben wird.

Natürlich wird es das einfachere fmt.Printm geben, das für jeden geklammerten Ausdruck den enthaltenen Wert ersetzt, wie er von fmt.Print wird.

Zum Beispiel:

    i, j := 1, 2
    fmt.Printm(m"i: {i}; j: {j}")

wird drucken

i: 1; j: 2

Probleme bei diesem Ansatz: die seltsame Verwendung eines m -Präfixes vor einem String-Literal; das duplizierte m im normalen Gebrauch – eines vor und eines nach der Klammer; die allgemeine Nutzlosigkeit einer m-Zeichenfolge, wenn sie nicht mit einer Funktion verwendet wird, die eine erwartet.

Vorteile: nicht zu schwer zu spezifizieren; unterstützt sowohl einfache als auch formatierte Interpolation; nicht auf fmt-Funktionen beschränkt, kann also mit Vorlagen oder unerwarteten Verwendungen funktionieren.

Wenn es eine typisierte Karte wäre (z. B. runtime.StringMap), könnten wir fmt.Print und Println verwenden, ohne das stotternde Printfm hinzuzufügen

Die Verwendung eines definierten Typs ist eine gute Idee, würde aber bei fmt.Printfm nicht helfen; Wir konnten den definierten Typ nicht als erstes Argument für fmt.Printf verwenden, da dies nur ein string .

Eine Sache, die früher im Thread von @runeimp erwähnt , aber nicht vollständig besprochen wurde, ist os.Expand :-

package main

import (
    "fmt"
    "os"
)

func main() {
    name := "foo"
    days := 12.312
    type m = map[string]string
    f := func(ph string) string {
        return m{"name": name, "days": fmt.Sprintf("%2.1f", days)}[ph]
    }
    fmt.Println(os.Expand("The gopher ${name} is ${days} days old.", f))
    // The gopher foo is 12.3 days old.
}

Obwohl dies für einfache Fälle zu ausführlich ist, ist es viel schmackhafter, wenn Sie eine große Anzahl von Werten haben, die interpoliert werden sollen (obwohl jeder Ansatz ein Problem mit 70 Werten hat!). Zu den Vorteilen gehören: -

  1. Wenn Sie eine Closure für die Mapping-Funktion verwenden, geht sie gut mit lokalen Variablen um.

  2. Es geht auch gut mit willkürlicher Formatierung um und hält diese aus der interpolierten Zeichenfolge selbst heraus.

  3. Wenn Sie in der interpolierten Zeichenfolge einen Platzhalter verwenden, der in der Mapping-Funktion nicht vorhanden ist, wird er automatisch durch eine leere Zeichenfolge ersetzt.

  4. Änderungen an der Mapping-Funktion sind relativ einfach vorzunehmen.

  5. Wir haben es bereits - es sind keine Sprach- oder Bibliotheksänderungen erforderlich.

@ianlancetaylor Diese Lösung klingt für mich nach einer soliden Option. Obwohl ich nicht sehe, warum wir alternative Druckmethoden benötigen. Ich übersehe wahrscheinlich etwas, aber es scheint eine einfache Signaturänderung mit interface{} und Typprüfung zu sein. OK, ich habe gerade festgestellt, dass sich die Signaturänderung für einen vorhandenen Code als sehr problematisch erweisen könnte. Aber wenn der grundlegende Mechanismus implementiert wurde und wir auch einen stringlit -Typ erstellt haben, der string oder m"str" darstellt, oder wenn m"str" auch string akzeptiert hat

Danke, dass du das noch einmal angesprochen hast, @alanfo , das sind alles ausgezeichnete Punkte. 😃

Ich habe os.Expand für leichte Vorlagen verwendet und es kann in Situationen sehr praktisch sein, in denen Sie sowieso eine Wertekarte erstellen müssen. Aber wenn die Karte nicht benötigt wird und Sie Ihre Schließung in mehreren verschiedenen Bereichen vornehmen müssten, nur um die lokalen Variablen für Ihre (jetzt viele Male kopierte) Ersatzfunktion zu erfassen, ignoriert DRY vollständig und kann zu Wartungsproblemen führen und fügt nur hinzu Mehr Arbeit, wenn interpolierte Zeichenfolgen "einfach funktionieren", diese Wartungsprobleme lindern und nicht das Erstellen einer Karte erfordern, nur um diese dynamische Zeichenfolge zu verwalten.

@runeimp Wir können die Signatur von fmt.Printf nicht ändern. Das würde die Go 1-Kompatibilität beeinträchtigen.

Die Vorstellung eines stringlit -Typs impliziert eine Änderung des Typsystems von Go, was eine viel größere Sache ist. Go hat absichtlich ein sehr einfaches Typsystem. Ich glaube nicht, dass wir es für dieses Feature verkomplizieren wollen. Und selbst wenn wir es täten, würde fmt.Printf immer noch ein string -Argument akzeptieren, und wir können das nicht ändern, ohne bestehende Programme zu beschädigen.

@ianlancetaylor Danke für die Klarstellung. Ich schätze den Wunsch, die Abwärtskompatibilität mit etwas so Grundlegendem wie dem fmt -Paket oder dem Typsystem nicht zu brechen. Ich hatte nur gehofft, dass es eine (für mich) versteckte Möglichkeit geben könnte, die irgendwie eine Option in dieser Richtung sein könnte. 👼

Ich mag die Ian-Art, dies zu implementieren. Würden Generika bei dem fmt.Print -Problem nicht helfen?

contract printable(T) {
  T string, map[string]string // or the type Brad suggested "runtime.StringMap"
}

// And then change the signature of fmt.Print to:
func Print(type T printable) (str T) error { 
  // ...
}

Auf diese Weise sollte die Go 1-Kompatibilität erhalten bleiben.

Für die Go 1-Kompatibilität können wir den Typ einer Funktion überhaupt nicht ändern. Funktionen werden nicht nur aufgerufen. Sie werden auch in Code wie verwendet

    var print func(...interface{}) = fmt.Print

Leute schreiben Code wie diesen, wenn sie Funktionstabellen erstellen oder wenn sie handgerollte Abhängigkeitsinjektionen für Tests verwenden.

Ich habe das Gefühl, dass strings.Replacer (https://golang.org/pkg/strings/#Replacer) fast eine String-Interpolation durchführen kann, nur fehlt die Interpolationskennung (zB ${...}) und die Musterverarbeitung (zB wenn var i int = 2 , sollte "${i+1}" im Ersetzer auf "3" abgebildet werden)

Ein weiterer Ansatz hätte eine eingebaute Funktion, sagen wir format("Ich bin ein %(foo)s %(bar)d"), die sich zu fmt.Sprintf("Ich bin ein %s %d", foo, Bar). Zumindest ist das voll abwärtskompatibel, FWIW.

Aus Sicht des Sprachdesigns wäre es eigenartig, eine eingebaute Funktion zu einem Verweis auf eine Funktion in der Standardbibliothek erweitern zu lassen. Um eine klare Definition für alle Implementierungen der Sprache bereitzustellen, müsste die Sprachspezifikation das Verhalten von fmt.Sprintf vollständig definieren. Was wir meiner Meinung nach vermeiden wollen.

Dies wird wahrscheinlich nicht alle glücklich machen, aber ich denke, das Folgende wäre das allgemeinste. Es ist in drei Teile gegliedert

  1. fmt.Printm-Funktionen, die eine Formatzeichenfolge und ein map[string]interface{} annehmen
  2. akzeptiere #12854, damit du map[string]interface{} fallen lassen kannst, wenn du es anrufst
  3. Namen ohne Schlüssel in Map-Literalen als Kurzform für "name": name, oder "qual.name": qual.name, zulassen

Zusammengenommen würde das so etwas erlauben

fmt.Printm("i: {i}; j: {j}", {i, j})
// which is equivalent to
fmt.Printm("i: {i}; j: {j}", map[string]interface{}{
  "i": i,
  "j": j,
})

Das hat immer noch die Duplizierung zwischen dem Format-String und den Argumenten, aber es ist viel leichter auf der Seite und es ist ein Muster, das leicht automatisiert werden kann: Ein Editor oder Tool könnte die {i, j} basierend auf dem String und dem Compiler automatisch ausfüllen würde Sie wissen lassen, wenn sie nicht im Geltungsbereich sind.

Das lässt Sie keine Berechnungen innerhalb der Formatzeichenfolge durchführen, was nett sein kann, aber ich habe das oft genug übertrieben gesehen, um es als Bonus zu betrachten.

Da es allgemein für Abbildungsliterale gilt, kann es auch in anderen Fällen verwendet werden. Ich benenne meine Variablen oft nach dem Schlüssel, den sie in der Karte haben, die ich baue.

Ein Nachteil davon ist, dass es nicht auf Strukturen angewendet werden kann, da diese entschlüsselt werden können. Dies könnte behoben werden, indem ein : vor dem Namen verlangt wird, z. B. {:i, :j} , und dann könnten Sie es tun

Field2 := f()
return aStruct{
  Field1: 2,
  :Field2,
}

Brauchen wir dafür eine sprachliche Unterstützung? So wie es jetzt ist, kann es so aussehen, entweder mit einem Kartentyp oder mit einer flüssigen, typsichereren API:

package main

import (
    "fmt"
    "strings"
)

type V map[string]interface{}

func Printm(format string, args V) {
    for k, v := range args {
        format = strings.ReplaceAll(format, fmt.Sprintf("{%s}", k), fmt.Sprintf("%v", v))
    }
    fmt.Print(format)
}

type Buf struct {
    sb strings.Builder
}

func Fmt(msg string) *Buf {
    res := Buf{}
    res.sb.WriteString(msg)
    return &res
}

func (b *Buf) I(val int) *Buf {
    b.sb.WriteString(fmt.Sprintf("%v", val))
    return b
}

func (b *Buf) F(val float64) *Buf {
    b.sb.WriteString(fmt.Sprintf("%v", val))
    return b
}

func (b *Buf) S(val string) *Buf {
    b.sb.WriteString(fmt.Sprintf("%v", val))
    return b
}

func (b *Buf) Print() {
    fmt.Print(b.sb.String())
}

func main() {
    Printm("Hello {k} {i}\n", V{"k": 22.5, "i": "world"})
    Fmt("Hello ").F(22.5).S(" world").Print()
}

https://play.golang.org/p/v9mg5_Wf-qD

Ok, es ist immer noch ineffizient, aber es sieht so aus, als wäre es überhaupt nicht viel Arbeit, ein Paket zu erstellen, das dies unterstützt. Als Bonus habe ich eine andere Fluid-API eingebaut, von der man sagen könnte, dass sie auch etwas Interpolationen simuliert.

Der „map string“-Vorschlag von @ianlancetaylor (obwohl ich persönlich „value/variable string“ mit av“...“-Syntax bevorzuge) erlaubt auch nicht formatierte Anwendungsfälle. Beispielsweise existiert #27605 (Überladungsfunktionen für Operatoren) weitgehend, da es heute schwierig ist, eine lesbare API für math/big und andere numerische Bibliotheken zu erstellen. Dieser Vorschlag würde die Funktion ermöglichen

func MakeInt(expression map[string]interface{}) Int {...}

Benutzt als

a := 5
b := big.MakeInt(m"100000")
c := big.MakeInt(m"{a} * ({b}^2)")

Wichtig ist, dass diese Hilfsfunktion mit der derzeit vorhandenen leistungsstärkeren und leistungsfähigeren API koexistieren kann.

Dieser Ansatz ermöglicht es der Bibliothek, beliebige Optimierungen für große Ausdrücke durchzuführen, und könnte auch ein nützliches Muster für andere DSLs sein, da er das Parsen von benutzerdefinierten Ausdrücken ermöglicht, während Werte weiterhin als Go-Variablen dargestellt werden. Insbesondere werden diese Anwendungsfälle von Pythons F-Strings nicht unterstützt, da die Interpretation der eingeschlossenen Werte von der Sprache selbst vorgegeben wird.

@HALtheWise Danke, das ist ziemlich ordentlich.

Ich wollte einen Kommentar abgeben, um ein wenig Unterstützung für diesen Vorschlag aus der Sicht eines allgemeinen Entwicklers zu zeigen. Ich codiere seit über 3 Jahren professionell mit golang. Als ich zu golang (von obj-c/swift) wechselte, war ich enttäuscht, dass die String-Interpolation nicht enthalten war. Ich habe C und C++ in der Vergangenheit über ein Jahrzehnt lang verwendet, daher möchte printf keine besondere Anpassung, außer dass ich das Gefühl habe, ein wenig rückwärts zu gehen - ich habe festgestellt, dass es tatsächlich einen Unterschied bei der Codepflege und Lesbarkeit macht für komplexere Saiten. Ich habe kürzlich ein bisschen Kotlin gemacht (für ein Gradle-Build-System), und die Verwendung von String-Interpolation war ein Hauch frischer Luft.

Ich denke, die String-Interpolation kann die String-Komposition für diejenigen, die neu in der Sprache sind, zugänglicher machen. Es ist auch ein Gewinn für die technische UX und Wartung, da die kognitive Belastung sowohl beim Lesen als auch beim Schreiben von Code reduziert wird.

Ich freue mich, dass dieser Vorschlag wirklich Beachtung findet. Ich freue mich auf die Auflösung des Vorschlags. =)

Wenn ich das richtig verstehe, lautet der Vorschlag von @ianlancetaylor :

i := 3
foo := m"twice i is %20{i * 2}s :)"
// the compiler will expand to:
foo := map[string]interface{}{
    "": "twice i is %20{i * 2}s :)",
    "i * 2": 6,
}

Danach verarbeitet eine Druckfunktion diese Karte, analysiert die gesamte Vorlage erneut und nutzt einige Vorteile der vorab analysierten Vorlage

Aber wenn wir m"str" ​​zu einer Funktion erweitern?

i := 3
foo := m"twice i is %20{i * 2}s :)"
// the compiler will expands to:
foo := m(
    []string{"twice i is ", " :)"}, // split string
    []string{"%20s"},               // formatter for each value
    []interface{}{6},               // values
)

Diese Funktion hat folgende Signatur:

func m(strings []string, formatters []string, values []interface{}) string {}

Diese Funktion wird eine bessere Leistung erbringen, da, um mehr Vorteile aus der vorgeparsten Vorlage zu ziehen, und viel mehr Optimierungen vorgenommen werden könnten, ähnlich wie Rust es mit der Funktion println! tut.

Was ich hier zu beschreiben versuche, ist den Tagged-Funktionen des Javascript sehr ähnlich, und wir könnten diskutieren, ob der Compiler Benutzerfunktionen akzeptieren sollte, um Strings zu formatieren, z.

foo.GQL"query { users{ %{expectedFields} } }"

bla.SQL`SELECT *
    FROM ...
    WHERE FOO=%{valueToSanitize}`

@rodcorsi Wenn ich Ihren Vorschlag richtig lese, muss die fmt.Printf -Formatierung in die eigentliche Sprache eingebaut werden, da der Compiler verstehen muss, wo %20s beginnt und endet. Das ist eines der Dinge, die ich zu vermeiden versuchte.

Beachten Sie auch, dass mein Vorschlag überhaupt nicht an die fmt.Printf -Formatierung gebunden ist und auch für andere Arten der Interpolation verwendet werden kann.

Ich wäre dagegen, m"..." als Erweiterung zu einem Funktionsaufruf zu behandeln, weil es verschleiert, was tatsächlich vor sich geht, und eine zweite Syntax für Funktionsaufrufe hinzufügt. Es erscheint im Allgemeinen vernünftig, eine strukturiertere Darstellung als eine Karte zu übergeben, um zu vermeiden, dass überall passende Neuimplementierungen des Parsing-Verhaltens erforderlich sind. Vielleicht eine einfache Struktur mit einem Slice konstanter String-Abschnitte, einem String-Slice für Dinge in geschweiften Klammern und einem Interface-Slice?

m"Hello {name}" -> 
struct{...}{
    []string{"Hello ", ""},
    []string{"name"},
    []interface{}{"Gopher"}

Die zweite und dritte Scheibe müssen gleich lang sein, und die erste muss eins länger sein. Es gibt auch andere Möglichkeiten, dies darzustellen, um diese Einschränkung strukturell zu codieren.
Der Vorteil gegenüber einem Format, das die ursprüngliche Zeichenfolge direkt verfügbar macht, besteht darin, dass die Anforderung, einen korrekten und leistungsfähigen Parser in der Funktion zu haben, die sie verwendet, weniger streng ist. Wenn es keine Unterstützung für Escape-Zeichen oder verschachtelte M-Strings gibt, ist das wahrscheinlich keine große Sache, aber ich möchte diesen Parser lieber nicht neu implementieren und testen, und das Zwischenspeichern des Ergebnisses könnte zu Laufzeit-Speicherlecks führen.

Wenn "Formatierungsoptionen" ein häufiger Wunsch von Dingen ist, die diese Syntax verwenden, könnte ich sehen, dass es einen Platz dafür in der Spezifikation gibt, aber ich persönlich würde mich für eine Syntax wie m"{name} is {age:%.2f} years old" , bei der der Compiler einfach alles übergibt nach dem : weiter zur Funktion.

Hallo, ich wollte dies kommentieren, um diesen Vorschlag zu unterstützen. Ich habe in den letzten 5 Jahren mit vielen verschiedenen Sprachen gearbeitet (Kotlin, Scala, Java, Javascript, Python, Bash, etwas C usw.) und ich lerne jetzt Go.

Ich denke, die String-Interpolation ist ein Muss in jeder modernen Programmiersprache, genauso wie die Typinferenz, und das haben wir in Go.

Für diejenigen, die argumentieren, dass Sie dasselbe mit Sprintf erreichen können, verstehe ich nicht, warum wir Typrückschluss in Go haben, Sie könnten dasselbe erreichen, indem Sie den Typ richtig schreiben? Nun ja, aber der Punkt hier ist, dass die String-Interpolation einen Großteil der Ausführlichkeit reduziert, die Sie benötigen, um dies zu erreichen, und leichter zu lesen ist (mit Sprintf müssen Sie zur Argumentliste und dem String hin und her springen, um einen Sinn zu finden die Saite).

In echter Software ist dies eine sehr geschätzte Funktion.

Ist es gegen das minimalistische Go-Design? Nein, es ist keine Funktion, die es Ihnen erlaubt, verrückte Dinge oder Abstraktionen zu tun, die Ihren Code verkomplizieren (wie Vererbung), es ist nur eine Möglichkeit, weniger zu schreiben und Klarheit zu schaffen, wenn Sie den Code lesen, was meiner Meinung nach nicht gegen das ist, was Go ist versuchen (wir haben Typrückschluss, wir haben den Operator := usw.).

Formatierung richtig gemacht für eine Sprache mit statischer Typisierung

Haskell verfügt über Bibliotheken und Spracherweiterungen für die Zeichenfolgeninterpolation. Das ist keine Typensache.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen