Go: proposition : interpolation de chaîne

Créé le 8 sept. 2019  ·  67Commentaires  ·  Source: golang/go

introduction

Pour ceux qui ne savent pas ce que c'est :

Rapide

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#
nom de chaîne = "Marque" ;
var date = DateHeure.Maintenant ;

Console.WriteLine($"Bonjour, {name} ! Aujourd'hui, c'est {date.DayOfWeek}, il est maintenant {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

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


renvoie les erreurs.Nouveau("ouverture du fichier de configuration : \{err}") // renvoie les erreurs.Nouveau("ouverture du fichier de configuration : " + erreur.Erreur())


var status fmt.Stringer

msg := "statut de sortie : {status}" // msg := "statut de sortie : " + 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 := "valeur"
fmt.Println("value='{v:a50}'") // value='<45 espaces>value'

# Conversions

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

tapez l'interface du formateur {
Format(chaîne de format) chaîne
}
```

peut être introduit plus tard pour gérer les options d'interpolation

Avantages et inconvénients par rapport au formatage traditionnel

Avantages

  • Type de sécurité
  • Performances (dépend du compilateur)
  • Prise en charge des options de formatage personnalisées pour les types définis par l'utilisateur

Les inconvénients

  • Complication d'un compilateur
  • Moins de méthodes de formatage prises en charge (pas %v (?), %T , etc.)
Go2 LanguageChange Proposal

Commentaire le plus utile

Pour moi, la vérification de type comme raison d'inclure l'interpolation de chaînes dans le langage n'est pas si convaincante. Il y a une raison plus importante, qui ne dépend pas de l'ordre dans lequel vous écrivez les variables dans fmt.Printf .

Prenons l'un des exemples de la description de la proposition et écrivons-le en Go avec et sans interpolation de chaîne :

  • Sans interpolation de chaîne (Go actuel)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
  • Fonctionnalité équivalente avec interpolation de chaîne (et mélange du commentaire @bradfitz , nécessaire pour exprimer les options de formatage)
name := "Mark"
date := time.Now()

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

Pour moi, la deuxième version est plus facile à lire et à modifier et moins sujette aux erreurs, car nous ne dépendons pas de l'ordre dans lequel nous écrivons les variables.

Lors de la lecture de la première version, il faut relire plusieurs fois la ligne pour vérifier qu'elle est correcte, en bougeant les yeux d'avant en arrière pour créer une cartographie mentale entre la position d'un "%v" et l'endroit où il faut mettre la variable.

J'ai corrigé des bogues dans plusieurs applications (non écrites en Go, mais avec le même problème) où les requêtes de base de données avaient été écrites avec beaucoup de '?' au lieu de paramètres nommés ( SELECT * FROM ? WHERE ? = ? AND ? != false ... ) et d'autres modifications (même par inadvertance lors d'une fusion git) ont inversé l'ordre de deux variables 😩

Donc, pour un langage dont le but est de faciliter la maintenabilité à long terme, je pense que cette raison vaut la peine d'envisager d'avoir une interpolation de chaîne

En ce qui concerne la complexité de ceci : je ne connais pas les rouages ​​du compilateur Go, alors prenez mon avis avec un grain de sel, mais _Ne pourrait-il pas simplement traduire la deuxième version montrée dans l'exemple ci-dessus vers la première ?_

Tous les 67 commentaires

Pourquoi ne pouvons-nous pas utiliser uniquement ${...} ? Swift a déjà une syntaxe, JavaScript et Kotlin une autre, C# encore une autre, Perl aussi... Pourquoi inventer une variante de plus ? Ne vaudrait-il pas mieux s'en tenir à celui le plus lisible et déjà existant ?

Pourquoi ne pouvons-nous pas utiliser uniquement ${...} ? Swift a déjà une syntaxe, JavaScript et Kotlin une autre, C# encore une autre, Perl aussi... Pourquoi inventer une variante de plus ? Ne vaudrait-il pas mieux s'en tenir à celui le plus lisible et déjà existant ?

Parce que nous pouvons avoir des chaînes existantes avec ${…} . Moi , par exemple.

Et \{ n'est pas autorisé pour le moment.

Cela ne semble pas avoir un gros avantage par rapport à appeler fmt.Sprintf .

Cela ne semble pas avoir un gros avantage par rapport à appeler fmt.Sprintf .

Oui, en plus d'une sécurité de type totale, de meilleures performances et d'une facilité d'utilisation. Formatage bien fait pour un langage avec typage statique.

Autant que je sache, cette proposition n'est pas plus sûre que d'utiliser fmt.Sprintf avec %v . Il est essentiellement identique en ce qui concerne la sécurité de type. Je suis d'accord qu'une mise en œuvre prudente pourrait avoir de meilleures performances dans de nombreux cas. Je suis agnostique sur la facilité d'utilisation.

Afin d'implémenter cela, nous devrions écrire, dans la spécification du langage, le formatage exact à utiliser pour tous les types. Nous aurions à décider et à documenter comment formater une tranche, un tableau, une carte. Une interface. Une chaîne. Ce serait un ajout significatif à la spécification de langue.

Je pense que c'est une de ces questions où les deux voies ont le droit d'exister, et c'est juste aux décideurs de décider. En Go, la décision a déjà été prise, il y a assez longtemps, et ça marche, et c'est idiomatique. C'est fmt.Sprintf avec %v .

Historiquement, il existe des langages où l'interpolation à l'intérieur d'une chaîne est présente depuis le tout début. C'est notamment Perl. C'est l'une des raisons pour lesquelles Perl est devenu si populaire, parce que c'était super pratique comparé à sprintf() dans C et al. Et le %v n'a pas encore été inventé. Et puis il y a des langues où l'interpolation était présente, en quelque sorte, mais était gênante syntaxiquement, pensez à "text" + v1 + "text" . JavaScript a ensuite introduit les littéraux entre guillemets, qui sont multilignes et prennent en charge l'interpolation d'expressions arbitraires à l'intérieur ${...} , ce qui représente une amélioration considérable par rapport à "text" + v1 + "text" . Le Go a aussi des littéraux multilignes backtick, mais sans ${...} . Qui a copié qui je ne sais pas.

Je ne suis pas d'accord avec @ianlancetaylor sur le fait que le soutien de cela nécessitera des efforts substantiels. En fait, fmt.Sprintf avec %v fait exactement cela, n'est-ce pas ? Cela ressemble pour moi à un wrapper de syntaxe différent pour exactement la même chose sous le capot. Ai-je raison?

Mais __je suis d'accord__ avec @ianlancetaylor que l'utilisation fmt.Sprintf avec %v est aussi pratique. C'est aussi exactement la même longueur à l'écran, et, ce qui est très important à mon humble avis - est déjà idiomatique pour Go. Cela fait en quelque sorte Go Go. Si nous copions-implémentons toutes les autres fonctionnalités de toutes les autres langues, alors ce ne sera plus Go, mais toutes les autres langues.

Il y a encore une chose. D'après ma longue expérience avec Perl, où l'interpolation de chaînes était présente dès le début, je peux dire que ce n'est pas si parfait. Il y a un problème avec ça. Pour les programmes simples et triviaux, et probablement pour 90% de tous les programmes, l'interpolation de chaînes de variables fonctionne très bien. Mais ensuite, de temps en temps, vous obtenez un var=1.99999999999 et vous voulez l'imprimer sous la forme 1.99 , et vous ne pouvez pas le faire avec une interpolation de chaîne standard. Vous devez soit effectuer une conversion au préalable, soit... Consultez la documentation et réapprenez la syntaxe sprintf() oubliée depuis longtemps. Ici __est__ le problème - l'utilisation de l'interpolation de chaîne vous permet d'oublier comment utiliser la syntaxe de type sprintf(), et probablement son existence même. Et puis, quand c'est nécessaire, vous passez trop de temps et d'efforts à faire les choses les plus simples. Je parle dans le contexte Perl, et c'était une décision du ou des concepteurs de langage de le faire.

Mais en Go, une décision différente a été prise. Le fmt.Sprintf avec %v est déjà là, c'est __aussi pratique, aussi court__ que les chaînes interpolées, et c'est __idiomatique__, en ce sens que c'est dans la documentation, dans les exemples, partout. Et il ne souffre pas du problème d'oublier éventuellement comment imprimer 1,99999999999 en 1,99.

L'introduction de la syntaxe proposée rendra Go un peu plus proche de Swift et/ou plus proche de JavaScript, et certains aimeront peut-être cela. Mais je pense que cette syntaxe particulière ne le rendra pas meilleur, sinon un peu pire.

Je pense que la façon existante d'imprimer les choses devrait rester telle quelle. Et si quelqu'un a besoin de plus, il existe des modèles pour cela.

Si une partie de l'argument ici est la sécurité au moment de la compilation, je ne suis pas d'accord que ce soit un argument convaincant; go vet , et par extension go test , ont signalé des utilisations incorrectes de fmt.Sprintf pendant un certain temps.

Il est également possible d'optimiser les performances aujourd'hui via go generate , si vous le souhaitez vraiment. C'est un compromis qui ne vaut pas la plupart du temps. J'ai l'impression qu'il en va de même pour élargir considérablement la spécification; le compromis n'en vaut généralement pas la peine.

Autoriser les appels de fonction à l'intérieur de chaînes interpolées serait malheureux - trop facile à manquer.
Ne pas les autoriser est un autre cas particulier inutile.

Si une partie de l'argument ici est la sécurité au moment de la compilation, je ne suis pas d'accord que ce soit un argument convaincant; go vet, et par extension go test, ont signalé des utilisations incorrectes de fmt.Sprintf pendant un certain temps. Il est également possible d'optimiser les performances aujourd'hui via go generate, si vous le souhaitez vraiment. C'est un compromis qui ne vaut pas la plupart du temps. J'ai l'impression qu'il en va de même pour élargir considérablement la spécification; le compromis n'en vaut généralement pas la peine.

Mais la sécurité du type doit être garantie par le compilateur, et non par l'outillage. C'est la sémantique; c'est comme dire qu'il devrait y avoir un outil pour vérifier où vous avez oublié de vérifier null au lieu d'avoir des options ou des valeurs nulles explicitement déclarées.

En dehors de cela, la seule façon pour que cela soit sûr est avec des types dépendants. L'interpolation de chaînes est juste plus de sucre syntaxique pour la même chose que fmt.Sprintf et, bien que je sois tout à fait en faveur d'un bon sucre, toute la communauté du go ne semble pas l'être.

Ou peut-être quelque chose comme modifier la langue pour mieux fonctionner avec fmt.Printf & friends.

Comme si fmt supportait quelque chose comme %(foo)v ou %(bar)q , alors dites que si un littéral de chaîne contenant %(<ident>) est utilisé dans un appel à une fonction/méthode variadique, alors tous les référencés les symboles sont automatiquement ajoutés à la liste variadique.

par exemple ce code :

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

serait vraiment compiler à:

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

Et fmt pourrait simplement ignorer les bits inutiles (name) et (age) .

C'est un changement de langue assez particulier, cependant.

Pour moi, la vérification de type comme raison d'inclure l'interpolation de chaînes dans le langage n'est pas si convaincante. Il y a une raison plus importante, qui ne dépend pas de l'ordre dans lequel vous écrivez les variables dans fmt.Printf .

Prenons l'un des exemples de la description de la proposition et écrivons-le en Go avec et sans interpolation de chaîne :

  • Sans interpolation de chaîne (Go actuel)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
  • Fonctionnalité équivalente avec interpolation de chaîne (et mélange du commentaire @bradfitz , nécessaire pour exprimer les options de formatage)
name := "Mark"
date := time.Now()

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

Pour moi, la deuxième version est plus facile à lire et à modifier et moins sujette aux erreurs, car nous ne dépendons pas de l'ordre dans lequel nous écrivons les variables.

Lors de la lecture de la première version, il faut relire plusieurs fois la ligne pour vérifier qu'elle est correcte, en bougeant les yeux d'avant en arrière pour créer une cartographie mentale entre la position d'un "%v" et l'endroit où il faut mettre la variable.

J'ai corrigé des bogues dans plusieurs applications (non écrites en Go, mais avec le même problème) où les requêtes de base de données avaient été écrites avec beaucoup de '?' au lieu de paramètres nommés ( SELECT * FROM ? WHERE ? = ? AND ? != false ... ) et d'autres modifications (même par inadvertance lors d'une fusion git) ont inversé l'ordre de deux variables 😩

Donc, pour un langage dont le but est de faciliter la maintenabilité à long terme, je pense que cette raison vaut la peine d'envisager d'avoir une interpolation de chaîne

En ce qui concerne la complexité de ceci : je ne connais pas les rouages ​​du compilateur Go, alors prenez mon avis avec un grain de sel, mais _Ne pourrait-il pas simplement traduire la deuxième version montrée dans l'exemple ci-dessus vers la première ?_

@ianlancetaylor a souligné que mon croquis ci-dessus (https://github.com/golang/go/issues/34174#issuecomment-532416737) n'est pas strictement rétrocompatible, car il pourrait y avoir de rares programmes où cela changerait leur comportement.

Une variante rétrocompatible consisterait à ajouter un nouveau type de "littéral de chaîne formatée", préfixé par, disons, un f :

par exemple ce code :

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

serait vraiment compiler à:

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

Mais alors le double f (un dans Printf suivi du f avant le nouveau type de littéral de chaîne) serait bégaiement.

Je ne comprends pas non plus le fonctionnement interne du compilateur, donc je suppose (peut-être bêtement) aussi que cela pourrait être implémenté dans le compilateur, de sorte que quelque chose comme
fmt.printf("Hello %s{name}. You are %d{age}")

compilerait à sa formulation actuelle équivalente.

L'interpolation de chaîne a l'avantage évident d'une plus grande lisibilité (une décision de conception de base de Go) et s'adapte également mieux à mesure que les chaînes que l'on traite deviennent plus longues et plus compliquées (une autre décision de conception de base de Go). Veuillez également noter que l'utilisation de {age} donne le contexte de la chaîne qu'il n'aurait pas autrement si vous ne faisiez que parcourir la chaîne (et bien sûr en ignorant le type qui a été spécifié), la chaîne aurait pu se terminer par "Vous êtes grand", "Vous êtes [à l'emplacement XXX]", "Vous travaillez trop dur" et à moins que vous ne mettiez l'énergie mentale pour mapper la méthode de formatage à chaque instance d'interpolation, il n'est pas immédiatement évident de savoir ce qui devrait y aller. En supprimant cet obstacle mental (certes minime), le programmeur peut se concentrer sur la logique plutôt que sur le code.

Le compilateur implémente la spécification de langage. La spécification de langue ne dit actuellement rien du tout sur le paquet fmt. Ce n'est pas nécessaire. Vous pouvez écrire de gros programmes Go qui n'utilisent pas du tout le package fmt. L'ajout de la documentation du package fmt à la spécification du langage la rendrait sensiblement plus grande, ce qui est une autre façon de dire que cela rend le langage beaucoup plus complexe.

Cela ne rend pas cette proposition impossible à adopter, mais c'est un coût important, et nous avons besoin d'un avantage important pour compenser ce coût.

Ou nous avons besoin d'un moyen de discuter de l'interpolation de chaînes sans impliquer le package fmt. C'est assez clair pour les valeurs de type chaîne, ou même de type []byte , mais beaucoup moins clair pour les valeurs d'autres types.

Je ne suis pas favorable à cette proposition en partie à cause de ce que @IanLanceTaylor a dit ci-dessus et en partie parce que, lorsque vous essayez d'interpoler des expressions complexes avec des options de formatage, tout avantage de lisibilité a tendance à disparaître.

De plus, on oublie parfois que la possibilité d'inclure des arguments variadiques dans la famille de fonctions fmt.Print (et Println ) permet déjà une forme d'interpolation. On peut facilement reproduire certains des exemples cités précédemment avec le code suivant qui, à mon sens, est tout aussi lisible :

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.")

Une autre raison de toujours l'ajouter et de l'avoir dans la langue : https://github.com/golang/go/issues/34403#issuecomment -542560071

Nous trouvons les commentaires de @alanfo dans https://github.com/golang/go/issues/34174#issuecomment -540995458 convaincants : vous pouvez utiliser fmt.Sprint pour faire une sorte simple d'interpolation de chaîne. La syntaxe est peut-être moins pratique, mais toute approche à cet égard nécessiterait un marqueur spécial pour les variables à interpoler dans tous les cas. Et, comme indiqué, cela permet un formatage arbitraire des valeurs à interpoler.

Comme indiqué ci-dessus, il existe un moyen de le faire approximativement qui permet même le formatage des variables individuelles. Il s'agit donc d'un déclin probable . Laissant ouvert pendant quatre semaines pour les commentaires finaux.

Je suis régulièrement confronté à la construction de blocs de texte avec plus de 50 variables à insérer. Plus de 70 dans quelques cas. Ce serait facile à maintenir avec les f-strings de Python (similaire à C # mentionné ci-dessus). Mais je gère cela en Go au lieu de Python pour plusieurs raisons. La configuration initiale de fmt.Sprintf pour gérer ces blocs est... ok. Mais Dieu m'en préserve, je dois corriger une erreur ou modifier le texte de quelque manière que ce soit, ce qui implique de déplacer ou de supprimer des marqueurs %anything et leurs variables liées à la position. Et construire manuellement des cartes pour passer à template ou configurer os.Expand n'est pas non plus une bonne option. Je vais prendre la vitesse (de configuration) et la facilité de maintenance des chaînes F sur fmt.Sprintf n'importe quel jour de la semaine. Et non, fmt.Sprint ne serait pas extrêmement bénéfique. Plus facile à configurer que fmt.Sprintf dans ce cas. Mais il perd une grande partie de sa signification visuelle parce que vous sautez dans et hors des cordes. "My {age} is not {quality} in this discussion" ne saute pas dans et hors des chaînes comme le fait "My ", age, " is not ", quality, " in this discussion" . Surtout au fil de plusieurs dizaines de références. Déplacer du texte et des références consiste simplement à copier et coller avec des f-strings. Les suppressions sont simplement sélectionner et supprimer. Parce que vous êtes toujours dans la chaîne. Ce n'est pas le cas lorsque vous utilisez fmt.Sprint . Il est très facile de sélectionner accidentellement (ou nécessairement) des virgules non-chaîne ou des terminaisons de chaîne entre guillemets doubles et de les déplacer pour casser le formatage et exiger des modifications pour le remettre en place. fmt.Sprint et fmt.Sprintf dans ces cas prennent beaucoup plus de temps et sont sujets aux erreurs que tout ce qui ressemble à des f-strings.

Cela ressemble à une tâche assez horrible, mais vous le faites!

Si j'étais confronté à quelque chose comme ça, alors je penserais certainement en termes de text/template au départ ou, s'il était trop difficile d'intégrer mes variables dans une structure ou une carte, je préférerais probablement fmt.Sprint à fmt.Sprintf mais organisez le code de la manière suivante :

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

Bien que cela occuperait beaucoup d'espace à l'écran, il serait relativement facile de modifier, de supprimer ou de déplacer des éléments sans trop de risque de se tromper.

Il existe quelques bibliothèques d'interpolation de chaînes go, mais sans fonctionnalités de langage telles que les types d'union ou les génériques, elles ne sont pas aussi flexibles ni fluides que dans d'autres langages :

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)
}

Vous pouvez également aller sur go.uber.org/yarpc/internal/interpolate

Encore une fois, créer une carte juste pour que je puisse utiliser template/text , une bibliothèque tierce ou un système minimal pour os.Expand est génial lorsque vous n'avez pas 50 clés ou plus à créer pour les variables qui existent déjà pour le reste du code en question. Et le seul véritable argument contre cette fonctionnalité est "vous pourriez oublier comment utiliser correctement le % de formatage" et pourriez avoir besoin de passer 2 minutes supplémentaires à utiliser Google pour rechercher le formatage pour la seule instance dont vous avez absolument besoin de l'efficacité, de la spécificité ou de quoi que ce soit de fmt.Sprintf ? D'un autre côté, vous avez peut-être presque toujours besoin de l'efficacité, de la spécificité ou de quoi que ce soit de fmt.Sprintf et n'oubliez jamais le formatage parce que vous l'utilisez toujours. Je ne vois pas le problème.

L'autre argument étant qu'il rend le langage plus complexe ? Oui. Techniquement, tout ajout au langage augmente sa complexité. Et je suis tout à fait pour ne pas rendre le langage plus complexe pour des raisons futiles. J'aimerais _VRAIMENT_ voir l'utilisation de in pour toutes les façons dont il est utilisé en Python. Mais je me contenterais de remplacer := range par in . Je n'ai pratiquement pas touché à Python au cours de la dernière année et je ne l'ai utilisé que deux ans auparavant. Mais neuf fois sur dix, je tape d'abord in puis je corrige avec := range . Mais je ne vais pas me battre pour celui-là. := range est suffisant et évident pour raisonner sur le code que vous avez écrit auparavant. Mais les f-strings et leur genre sont quelque chose de complètement différent. C'est la fonctionnalité, la convivialité et la maintenabilité du noyau dur. Cela devrait absolument faire partie de la spécification Go. S'il y avait une possibilité de le faire en tant que bibliothèque tierce, je créerais la bibliothèque et j'en finirais avec elle. Mais à ma connaissance, ce n'est même pas faisable sans une extension supplémentaire du langage Go. Donc, à tout le moins, je pense que l'expansion de la langue devrait être en place.

@runeimp Je tiens à préciser que votre suggestion selon laquelle nous pourrions commencer à prendre en charge "My {age} is not {quality} in this discussion" ne fonctionne pas. C'est une chaîne Go valide aujourd'hui, et si nous nous attendions à interpoler age et quality , cela casserait les programmes Go existants. Il faudrait que ce soit quelque chose de plus comme "My \{age} is not \{quality} in this discussion" . Et les problèmes de formatage des valeurs non-chaîne demeurent.

J'apprécie que @ianlancetaylor. Et juste pour être clair, les f-strings Python sont f"My {age} is not {quality} in this discussion" ou F'My {age} is not {quality} in this discussion' ou toute combinaison de n'importe quel cas f et un guillemet simple ou double. Comme mentionné, très semblable à la façon dont C # semble le faire. De plus, bien que je préfère le style Python f-string ou C #, j'accepterai absolument _ANYTHING_ qui facilite une utilisation similaire. InterPolationACTIVATE"My @$@age###^&%$ is not @$@quality###^&%$ in this discussion"INTERpolationDEACTVATEandNullify serait acceptable. Lamentable, mais acceptable. Et je ne plaisante même pas. Je prendrais cette excuse boiteuse pour l'interpolation de chaîne sur fmt.Sprintf n'importe quel jour de la semaine. Les avantages de l'entretien le justifieraient à eux seuls.

Je comprends que cela aiderait votre cas particulier.

Dans https://blog.golang.org/go2-here-we-come @griesemer a écrit que tout changement de langue devrait

  1. aborder un problème important pour de nombreuses personnes,
  2. avoir un impact minimal sur tout le monde, et
  3. venir avec une solution claire et bien comprise.

Est-ce un problème important pour beaucoup de gens?

Je n'en ai aucune idée mais je m'attendrais à ce que ce soit le cas. Je pense que c'est une fonctionnalité qui n'est pas importante pour quiconque n'a jamais eu à gérer que 5 références ou moins sur une courte chaîne. Ou toujours dû le gérer avec des modèles de toute façon en raison d'autres exigences de spécification. Mais pour ceux d'entre nous qui l'utilisent. Il est assez cruellement manqué lorsqu'il n'est pas présent. Et pour ceux qui n'ont jamais eu l'option (n'ont jamais utilisé que C, C++, etc. avant Go), il peut être très difficile de comprendre les avantages à un niveau purement théorique car les exemples de leur expérience sont probablement rapidement oubliés ou seulement rappelés comme les pires projets auxquels ils ont dû faire face en essayant d'oublier les points douloureux. Je doute que vous voyiez jamais une réponse majoritaire concernant le problème à moins que la plupart des développeurs Go ne viennent de langages qui prennent en charge l'interpolation de chaînes de variables locales.

C'est un problème que j'ai toujours rencontré depuis que j'ai commencé en tant que développeur web professionnel il y a plus de 20 ans. Dans la plupart de ces cas, les modèles étaient courants pour le front-end et le back-end. À la fin de cette partie de ma carrière, j'ai fini par travailler sur Ruby on Rails et je suis immédiatement tombé amoureux de l'interpolation de chaînes "My #{age} is not #{quality} in this discussion" de Ruby. Même si la syntaxe livre-accolade m'a semblé très étrange au début. Lorsque je suis passé à l'ingénierie d'intégration, j'utilisais principalement Python 3 et j'étais beaucoup plus heureux d'utiliser son nouveau système str.format() au lieu de ses anciennes chaînes au format %. Avec celui-là, vous feriez quelque chose comme "My {} is not {} in this discussion".format(age, quality) . Ainsi, l'utilisation de références agnostiques au moins de type n'avait pas d'importance pour 90% des cas d'utilisation. Ce qui est juste plus simple à comprendre et ne concerne que l'index. Références également nommées comme "My {age} is not {quality} in this discussion".format(age=my_age_var, quality=my_quality_var) . Maintenant, si le même var est référencé 30 fois, vous n'avez qu'à le spécifier une fois dans les paramètres et il est facile de garder une trace, de copier et coller ou de supprimer. Les paramètres nommés (en entrée) sont une autre fonctionnalité de Python qui me manque dans Go. Mais je peux m'en passer si besoin est. Mais je suis retombé amoureux des f-strings (introduits dans Python 3.6) au moment où j'ai posé les yeux dessus. L'interpolation de chaînes m'a toujours facilité la vie.

@runeimp Pourriez-vous publier un exemple de l'une de ces 50+ interpolations de chaînes variables (dans n'importe quelle langue que vous avez) ? Je pense que cela pourrait aider la discussion d'avoir un exemple réel du code en question.

OK, ce n'est pas le code final, c'est un exemple simple, il n'a que 50 références et les noms des variables ont été modifiés pour protéger les innocents.

Aller 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,
    )
}

Un exemple potentiel de F-Strings

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}`,
    )
}

Lequel des deux aimeriez-vous maintenir et ajouter, mettre à jour, supprimer tous les mois pendant les prochaines années ?

Puis-je choisir ni l'un ni l'autre ? Les deux me paraissent horribles.

Qu'en est-il de votre cas qui ne peut pas être réglé avec fmt.Sprintf("%#v", dt) ? Autrement dit, quelle est l'exigence de commande? Formatage exact (par exemple = vs : pour les séparateurs, %q vs. %v , ...) ? Champs non imprimés ? Nouvelles lignes ?

Est-ce qu'un autre programme analyse la sortie, ou est-ce pour la consommation humaine?

Qu'en est-il de l'utilisation de la réflexion ?

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 += " "
    }
}

Ces deux options sont raisonnables pour cet exemple très spécifique. Mais cela ne va pas voler pour quelque chose de plus complexe. Ou littéralement n'importe quoi d'autre. Comme lorsque la même référence est utilisée plusieurs fois dans la même chaîne. Ou les valeurs ne font pas partie d'une structure ou d'une carte unique, mais sont générées dans la même portée. Ceci est juste un exemple maladroit. Malheureusement, je ne peux pas montrer de code réel. Mais il suffit de regarder n'importe quelle page Web contenant des centaines de mots. Ouvrez la vue source et imaginez que plus de 50 mots doivent être dynamiques. Imaginez maintenant que ce n'est pas pour une page Web, vous n'avez pas besoin d'un système de modèles pour une autre raison, mais pour éventuellement résoudre ce problème, toutes les variables sont déjà dans le champ d'application et sont utilisées dans d'autres parties du code et/ou à plusieurs endroits. dans ce bloc de code, et ce texte peut changer toutes les 3 à 4 semaines de manière parfois importante et très significative. Cela peut faire partie d'un système qui doit _occasionnellement_ générer tout ou partie des fichiers PDF, CSV, SQL INSERT, e-mail, appel API, etc.

Sans exemples concrets, il est vraiment difficile de dire si cette proposition est la bonne solution au problème. Peut-être que l'interpolation de chaîne est vraiment la solution, ou peut-être que nous rencontrons le problème XY . Peut-être que la bonne solution est déjà dans la langue quelque part, ou la solution est un changement de package reflect, ou peut-être un package text/template.

Premièrement, je doute que ce soit une question importante pour beaucoup de gens. C'est la seule discussion sérieuse que je me souvienne d'avoir vue à ce sujet et le vote emoji ne suggère pas un soutien écrasant. Les bibliothèques tierces ne sont pas non plus très nombreuses.

Deuxièmement, il convient de souligner que même fmt.Printf (et sa famille) peuvent faire des arguments répétés :

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

Troisièmement, je ne pense pas que ce soit nécessairement le cas pour les personnes qui ont utilisé l'interpolation de chaînes dans d'autres langages qui voudraient la voir en Go. Je l'ai déjà utilisé dans plusieurs langues et, même si c'est bien quand vous voulez interpoler des variables simples, dès que vous essayez d'utiliser des expressions plus complexes, des appels de fonction, des caractères de drapeau échappés, etc., puis ajoutez des détails de formatage à tout cela (ce qui signifie inévitablement une approche de style 'printf'), le tout peut rapidement devenir un gâchis illisible.

Enfin, il me semble que, lorsque vous avez un grand nombre de variables à interpoler, alors text/template est la meilleure approche et peut-être devrions-nous donc chercher des moyens de rendre cela plus pratique à utiliser lorsque les variables sont ne fait pas partie d'une structure bien définie plutôt que d'intégrer quelque chose de cette complexité dans la langue elle-même.

Vous pouvez également créer un DSL :

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()
}

ou

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()
}

Je peux profondément comprendre l'opinion de @ianlancetaylor . Nous savons que nous pouvons vivre avec fmt. Printf comme nous l'avons toujours fait.

En même temps, je suis tout à fait d'accord avec l' avis d' @alanfo .

plus facile à lire et à modifier et moins sujet aux erreurs

C'est très important pour une programmation système robuste.

Je pense qu'adopter des fonctionnalités/idées d'autres langages n'est pas du tout une honte quand cela peut faire gagner un temps précieux aux gens. En fait, beaucoup de gens souffrent de problèmes d'interpolation de chaînes.
C'est la raison pour laquelle d'autres langages modernes adoptent l'interpolation de chaînes.

Je ne veux pas que Go reste juste un peu meilleur langage que C.
Je pense que Go v2 est une chance d'être bien meilleur Go.

image

À mon humble avis, si la fonction d'interpolation de chaîne est fournie, la plupart d'entre nous choisiront de l'utiliser. Nous pouvons le sentir.

ps.
Malheureusement, le cas de @runeimp est un cas trop marginal. Je peux ressentir la douleur de la circonstance. Mais cela brouille les idées de proposition. (Sans vouloir vous offenser)

@doortts aucune infraction prise. J'apprécie toujours la conversation honnête.

Je savais que publier du code réel pouvait faire une brèche dans l'argument. Et je savais aussi que publier une approximation qui nécessite un peu de créativité pour se développer mentalement allait probablement simplement brouiller la discussion. Alors j'ai pris un pari parce que je ne m'attends pas à ce que quelqu'un d'autre le fasse. Mais partager du code réel n'est pas une option et les heures qu'il faudrait pour mettre cela en noir sur blanc avec un exemple concret, mais sinon, pour lesquelles je n'ai aucune utilité, ce n'est tout simplement pas quelque chose pour lequel j'ai l'énergie en ce moment. Je sais déjà bien que cet argument est difficile à cause des raisons que j'ai déjà énoncées auparavant. Je sais que je ne fais pas partie d'une douzaine de personnes qui voient la valeur réelle de cette fonctionnalité. Mais la plupart d'entre eux sont presque certainement loin de ce fil. Je suis ici uniquement parce que j'ai vu un article à ce sujet sur Reddit. Sinon, j'étais presque complètement inconscient de ce processus. J'ai été assez satisfait de la façon dont les choses ont avancé avec Go sans ressentir le besoin de participer à la conversation. Mais je voulais aider à cette discussion parce que je savais qu'il n'y aurait tout simplement pas beaucoup de voix qui se battraient simplement en raison de la quantité d'inertie à surmonter chaque fois qu'un changement de langage est suggéré. Je ne pense pas avoir jamais vu une opposition aussi forte aux changements dans une langue. Et cela rend très intimidant de publier à l'appui de quelque chose de nouveau. Je ne dis pas qu'aucune langue ne devrait accepter toutes les demandes de nouvelles fonctionnalités sans examen. Juste que la raison pour laquelle cela peut sembler calme du côté du support n'est pas toujours parce que le désir n'est pas là. Et avec cela, je demande à toute personne intéressée par cette fonctionnalité de publier _quelque chose_. N'importe quoi , afin que nous puissions voir de vrais chiffres à ce sujet.

Je pense que parce que c'est les vacances, il y a moins d'attention en général. L'interpolation de chaînes est extrêmement utile et souhaitée du point de vue du développeur (c'est pourquoi elle existe dans tant d'autres langages), mais il semble que cette proposition soit sous-cuite pour un changement aussi important à ce stade, qui, si elle est rejetée, je ne le pense pas signifie qu'il ne vaut pas la peine de revenir à l'avenir. Les petits changements progressifs qui rendraient cela évident ne sont pas encore en place.

La résolution dynamique des variables par leur nom à partir de la portée n'est pas quelque chose que je pense possible (bien que Yaegi Eval semble le faire ?) Actuellement.

Je pense que le message auquel @runeimp fait référence est mon message dans r/golang https://www.reddit.com/r/golang/comments/d1199a/why_is_there_no_equivalent_to_f_strings_in_python/
Où il y a un peu plus de discussion.

Je voulais juste jeter mon chapeau dans le ring et dire que l'interpolation variable me serait extrêmement utile, venant du monde python, quelque chose comme fmt.sprintf rend Go sérieusement maladroit et horrible à lire/maintenir.

Go est surtout très élégant dans la façon dont il rend les choses qu'il valorise très faciles et puissantes. La lisibilité et la maintenabilité sont quelques-uns des principaux locataires de sa philosophie et il ne devrait vraiment pas être si difficile de lire une chaîne et de savoir ce qui s'y passe. Il y a une surcharge très importante qui vient avec la compréhension de ce qui sera imprimé tout en gardant à l'esprit quelles variables sont mappées à quels éléments d'une liste à la fin de cette chaîne. Nous ne devrions tout simplement pas avoir besoin de maintenir cette surcharge mentale.

Si les gens pensent que fmt.sprintf leur convient, il peut être utilisé. Je n'ai pas l'impression que le fait d'avoir les deux nuit à Go en tant que langage puisque la proposition est incontestablement plus lisible que la méthode actuelle, intuitive et plus facile à maintenir. Cela ne devrait pas être justifié par des interpolations de plus de 50 variables, c'est une amélioration avec des interpolations d'une seule variable.

Est-ce quelque chose comme

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

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

vraiment une perspective préjudiciable à la langue? Je suis heureux d'avoir tort, mais honnêtement, je ne vois que des avantages dans cette proposition (ou quelque chose comme ça), à l'exception de l'incompatibilité ascendante, où quelque chose comme l'implémentation de cela dans une nouvelle version majeure de Go a du sens

Pourrait-il être envisagé de laisser cette discussion ouverte pendant une autre série de 4 semaines, puisqu'une partie importante de la communauté était en vacances pour Thanksgiving, Noël et le Nouvel An lorsque la coupure a été proposée ?

Il y a eu beaucoup plus de discussions depuis https://github.com/golang/go/issues/34174#issuecomment -558844640, donc je retire cela de la période de commentaire final.

Je suis enclin à être d'accord avec @ randall77 que je n'ai pas encore vu d'exemple convaincant ici. @runeimp , merci d'avoir posté l'exemple de code, mais il semble difficile à lire et difficile à modifier de toute façon. Comme le suggère @egonelbre , si nous voulons rendre cela plus maintenable, la première étape semble être de trouver une approche entièrement différente.

@cyclingwithelephants fmt.sprintf("I am %T{age} years old") n'est pas sur la table ici. Le langage ne fournit aucun mécanisme que fmt.Sprintf pourrait utiliser pour résoudre age . Go est un langage compilé et les noms de variables locales ne sont pas disponibles au moment de l'exécution. Ce serait plus acceptable si nous pouvions trouver un moyen de faire fonctionner cela, peut-être dans le sens de la suggestion de @bradfitz ci-dessus.

Merci @ianlancetaylor d'avoir levé l'étiquette de la période de commentaire final. 😀

Je pense que l'idée de @bradfitz était géniale. Je suppose qu'il y a des limitations potentielles quoi qu'il en soit sans un contexte de variable locale, mais j'accepterais volontiers ces limitations par rapport à l'absence d'interpolation de chaîne. Bien que j'aie un respect fou pour les mises à jour du formatage en pourcentage dans Go (j'adore les ajouts de %q , %v et %#v ), ce paradigme est ancien. Ce n'est pas parce que c'est vénérable que c'est la meilleure façon de faire les choses. Tout comme la façon dont Go gère la compilation, et en particulier la compilation croisée est _WAY_ meilleure que la façon dont cela se fait avec C ou C++. Maintenant, Go cache-t-il simplement toutes les options de compilateur désagréables avec des valeurs par défaut saines et c'est tout aussi moche sous le capot. Je ne sais pas précisément mais je crois que c'est le cas. Et c'est bien. C'est tout à fait acceptable pour moi. Je me fiche du sombre rituel que le compilateur fait pour que la fonctionnalité fonctionne. Je sais juste que la fonctionnalité rend la vie plus facile. Et m'a facilité la vie en tant que développeur dans tous les langages que j'ai utilisés et qui le prennent en charge. Et c'est toujours pénible dans les langues qui ne le supportent pas. C'est beaucoup plus facile à retenir que d'utiliser les 30+ caractères spéciaux dans le formatage en pourcentage pour 98% du formatage de chaîne dont j'ai besoin. Et dire d'utiliser %v au lieu de "le format de pourcentage correct" n'est pas la même facilité d'utilisation pour la création ni même la même facilité de maintenance.

Maintenant qu'il y a un peu plus de temps, je vais travailler sur un exemple et voir si je peux trouver des articles un peu plus éclairants que je ne l'ai été pour aider à illustrer les avantages significatifs de l'efficacité du travail pour ceux d'entre nous qui s'occupent d'interface humaine , la génération de documents et de code et la manipulation de chaînes sur une base régulière.

Voici une pensée qui peut mener à quelque chose de réalisable. Même si je ne sais pas si c'est une bonne idée.

Ajoutez un nouveau type de chaîne m"str" (et peut-être la même chose avec une chaîne brute). Ce nouveau type de littéral de chaîne est évalué à un map[string]interface{} . La recherche de la chaîne vide dans la carte vous donne la chaîne littérale elle-même. Le littéral de chaîne peut contenir des expressions entre accolades. Une expression entre accolades est évaluée comme si elle n'était pas dans le littéral de chaîne, et la valeur est stockée dans la carte avec la clé étant la sous-chaîne qui apparaît entre les accolades.

Par exemple:

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

Cela imprimera

twice i is {i * 2}
2

Dans le littéral de chaîne, les accolades peuvent être échappées avec une barre oblique inverse pour indiquer une simple accolade. Une accolade sans guillemets, sans correspondance, est une erreur de compilation. Il s'agit également d'une erreur de compilation si l'expression entre accolades ne peut pas être compilée. L'expression doit correspondre à exactement une valeur, mais n'est par ailleurs pas restreinte. La même chaîne entre accolades peut apparaître plusieurs fois dans le littéral de chaîne ; elle sera évaluée autant de fois qu'elle apparaîtra, mais une seule des évaluations sera stockée dans la carte (car elles auront toutes la même clé). Exactement lequel est stocké n'est pas spécifié (ceci est important si l'expression est un appel de fonction).

En soi, ce mécanisme est particulier mais inutile. Son avantage est qu'il peut être clairement spécifié et ne nécessite sans doute pas d'ajouts excessifs au langage.

L'utilisation s'accompagne de fonctions supplémentaires. La nouvelle fonction fmt.Printfm fonctionnera exactement comme fmt.Printf , mais le premier argument ne sera pas un string mais plutôt un map[string]interface{} . La "" dans la carte sera une chaîne de format. La chaîne de format prendra en charge, en plus des choses habituelles % , un nouveau modificateur {str} . L'utilisation de ce modificateur signifie qu'au lieu d'utiliser un argument pour la valeur, str sera recherché dans la carte, et cette valeur sera utilisée.

Par exemple:

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

imprimera la chaîne hi transmise à 20 espaces.

Naturellement, il y aura le plus simple fmt.Printm qui substituera à chaque expression entre accolades la valeur contenue telle qu'imprimée par fmt.Print .

Par exemple:

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

va imprimer

i: 1; j: 2

Problèmes avec cette approche : l'utilisation étrange d'un préfixe m avant un littéral de chaîne ; le double m en utilisation normale--un avant et un après la parenthèse ; l'inutilité générale d'une chaîne m lorsqu'elle n'est pas utilisée avec une fonction qui en attend une.

Avantages : pas trop difficile à spécifier ; prend en charge l'interpolation simple et formatée ; non limité aux fonctions fmt, il peut donc fonctionner avec des modèles ou des utilisations imprévues.

S'il s'agissait d'une carte typée (par exemple runtime.StringMap), nous pourrions utiliser fmt.Print et Println sans ajouter le bégaiement Printfm

Utiliser un type défini est une bonne idée, mais cela n'aiderait pas avec fmt.Printfm ; nous ne pouvions pas utiliser le type défini comme premier argument de fmt.Printf , car cela ne prend qu'un string .

Une chose qui a été mentionnée plus tôt dans le fil par @runeimp mais qui n'a pas été entièrement discutée est 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.
}

Bien que ce soit trop verbeux pour les cas simples, c'est beaucoup plus acceptable lorsque vous avez un grand nombre de valeurs à interpoler (bien que toute approche ait un problème avec 70 valeurs !). Les avantages incluent :-

  1. Si vous utilisez une fermeture pour la fonction de mappage, elle traite bien les variables locales.

  2. Il traite également très bien le formatage arbitraire et le garde hors de la chaîne interpolée elle-même.

  3. Si vous utilisez un espace réservé dans la chaîne interpolée qui n'est pas présent dans la fonction de mappage, il est automatiquement remplacé par une chaîne vide.

  4. Les modifications de la fonction de mappage sont relativement faciles à effectuer.

  5. Nous l'avons déjà - aucun changement de langue ou de bibliothèque n'est nécessaire.

@ianlancetaylor cette solution me semble être une option solide. Bien que je ne vois pas pourquoi nous avons besoin d'autres méthodes d'impression. J'oublie probablement quelque chose, mais cela semble être un simple changement de signature en utilisant interface{} et une vérification de type. OK, je viens de réaliser à quel point le changement de signature pouvait s'avérer très problématique pour certains codes existants. Mais si le mécanisme de base a été implémenté et que nous avons également créé un type stringlit qui représente string ou m"str" ou si m"str" a également accepté string serait-ce un changement de rupture acceptable pour Go v2 ? Ce sont tous les deux des "littéraux de chaîne", c'est juste que l'un d'eux a essentiellement un indicateur qui permet des fonctionnalités supplémentaires, non ?

Merci d'avoir soulevé cela à nouveau @alanfo , ce sont tous d'excellents points. 😃

J'ai utilisé os.Expand pour les modèles légers et cela peut être très pratique dans les situations où vous devez de toute façon créer une carte de valeurs. Mais si la carte n'est pas nécessaire, et que vous auriez besoin de faire votre fermeture dans plusieurs zones différentes juste pour capturer les variables locales pour votre fonction de remplacement (maintenant copiée plusieurs fois) ignore complètement DRY et peut entraîner des problèmes de maintenance et ajoute juste plus de travail était que les chaînes interpolées "fonctionneraient simplement", réduiraient ces problèmes de maintenance et ne nécessiteraient pas la création d'une carte uniquement pour gérer cette chaîne dynamique.

@runeimp Nous ne pouvons pas changer la signature de fmt.Printf . Cela romprait la compatibilité Go 1.

La notion de type stringlit implique de changer le système de type de Go, ce qui est beaucoup plus important. Go a intentionnellement un système de type très simple. Je ne pense pas que nous voulions compliquer les choses pour cette fonctionnalité. Et même si nous le faisions, fmt.Printf prendrait toujours un argument string , et nous ne pouvons pas changer cela sans casser les programmes existants.

@ianlancetaylor Merci pour la clarification. J'apprécie le désir de ne pas casser la rétrocompatibilité avec quelque chose d'aussi fondamental que le package fmt ou le système de type. J'espérais juste qu'il pourrait y avoir une possibilité cachée (pour moi) qui pourrait être une option dans ce sens d'une manière ou d'une autre. 👼

J'aime vraiment la façon dont Ian implémente cela. Les génériques n'aideraient-ils pas avec le problème fmt.Print ?

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 { 
  // ...
}

De cette façon, la compatibilité Go 1 devrait être préservée.

Pour la compatibilité Go 1, nous ne pouvons pas du tout changer le type d'une fonction. Les fonctions ne sont pas seulement appelées. Ils sont également utilisés dans le code comme

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

Les gens écrivent du code comme celui-ci lors de la création de tableaux de fonctions ou lors de l'utilisation d'injection de dépendances manuelle pour les tests.

J'ai le sentiment que strings.Replacer (https://golang.org/pkg/strings/#Replacer) peut presque faire une interpolation de chaîne, manquant juste l'identifiant d'interpolation (par exemple ${...}) et le traitement du modèle (par exemple si var i int = 2 , "${i+1}" doit être mappé sur "3" dans le remplaçant)

Encore une autre approche aurait une fonction intégrée, disons, format("Je suis un %(foo)s %(bar)d") qui se développe en fmt.Sprintf("Je suis un %s %d", foo, bar). Au moins, c'est entièrement rétrocompatible, FWIW.

Du point de vue de la conception du langage, il serait particulier d'avoir une fonction intégrée étendue à une référence à une fonction dans la bibliothèque standard. Pour fournir une définition claire de toutes les implémentations du langage, la spécification du langage devrait définir entièrement le comportement de fmt.Sprintf . Ce que je pense que nous voulons éviter.

Cela ne rendra probablement pas tout le monde heureux, mais je pense que ce qui suit serait le plus général. Il est divisé en trois parties

  1. Fonctions fmt.Printm qui prennent une chaîne de format et un map[string]interface{}
  2. acceptez #12854 afin que vous puissiez supprimer le map[string]interface{} lorsque vous l'appelez
  3. autoriser les noms sans clé dans les littéraux de carte comme raccourci pour "name": name, ou "qual.name": qual.name,

Pris ensemble, cela permettrait quelque chose comme

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,
})

Cela a toujours la duplication entre la chaîne de format et les arguments mais c'est beaucoup plus léger sur la page et c'est un modèle qui est facilement automatisé : un éditeur ou un outil pourrait remplir automatiquement le {i, j} en fonction de la chaîne et du compilateur vous ferait savoir s'ils ne sont pas dans le champ d'application.

Cela ne vous permet pas de faire des calculs dans la chaîne de format, ce qui peut être agréable, mais j'ai vu cela exagéré suffisamment de fois pour le considérer comme un bonus.

Puisqu'il s'applique aux littéraux de carte en général, il peut être utilisé dans d'autres cas. Je nomme souvent mes variables d'après la clé qu'elles seront dans la carte que je construis.

Un inconvénient de ceci est qu'il ne peut pas s'appliquer aux structures puisque celles-ci peuvent être sans clé. Cela pourrait être rectifié en exigeant un : avant le nom comme {:i, :j} et ensuite vous pourriez faire

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

Avons-nous besoin d'un support linguistique pour cela ? Dans l'état actuel des choses, cela peut ressembler à ceci, soit avec un type de carte, soit avec une API fluide et plus sécurisée :

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, c'est toujours inefficace, mais il semble que ce n'est pas beaucoup de travail pour faire un paquet qui supporte cela. En prime, j'ai inclus une API fluide différente qui pourrait aussi simuler quelque peu les interpolations.

La proposition "map string" de @ianlancetaylor (bien que je préfère personnellement la syntaxe "value/variable string" avec av"...") permet également des cas d'utilisation non formatés. Par exemple, #27605 (fonctions de surcharge d'opérateur) existe en grande partie parce qu'il est difficile aujourd'hui de créer une API lisible pour math/big et d'autres bibliothèques numériques. Cette proposition permettrait à la fonction

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

Utilisé comme

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

Surtout, cette fonction d'assistance peut coexister avec l'API plus performante et puissante qui existe actuellement.

Cette approche permet à la bibliothèque d'effectuer toutes les optimisations qu'elle souhaite pour les grandes expressions, et peut également être un modèle utile pour d'autres DSL, car elle permet l'analyse d'expressions personnalisées tout en représentant les valeurs en tant que variables Go. Notamment, ces cas d'utilisation ne sont pas pris en charge par les f-strings de Python car l'interprétation des valeurs incluses est imposée par le langage lui-même.

@HALtheWise Merci, c'est plutôt chouette.

Je voulais commenter pour montrer un peu de soutien à cette proposition, du point de vue d'un développeur général. Je code avec golang depuis plus de 3 ans professionnellement. Lorsque je suis passé à golang (depuis obj-c/swift), j'ai été déçu que l'interpolation de chaîne ne soit pas incluse. J'ai utilisé C et C++ pendant plus d'une décennie dans le passé, donc printf ne souhaite pas un ajustement particulier, autre que l'impression de revenir en arrière un peu - j'ai constaté que cela fait effectivement une différence avec la maintenance et la lisibilité du code pour les chaînes plus complexes. J'ai récemment fait un peu de kotlin (pour un système de construction progressive), et l'utilisation de l'interpolation de chaîne était une bouffée d'air frais.

Je pense que l'interpolation de chaînes peut rendre la composition de chaînes plus accessible pour ceux qui découvrent le langage. C'est également une victoire pour l'UX technique et la maintenance, en raison de la réduction de la charge cognitive à la fois lors de la lecture et de l'écriture de code.

Je suis heureux que cette proposition fasse l'objet d'un réel examen. J'attends avec impatience la résolution de la proposition. =)

Si je comprends bien, la proposition de @ianlancetaylor est :

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,
}

Après cela, une fonction d'impression gérera cette carte, analysera à nouveau le modèle entier et tirera quelques avantages du modèle pré-analysé.

Mais si nous étendons m"str" ​​à une fonction ?

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
)

Cette fonction a la signature suivante :

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

Cette fonction fonctionnera mieux car, pour tirer davantage parti du modèle pré-parsé, et beaucoup plus d'optimisations pourraient être effectuées de la même manière que Rust le fait avec la fonction println! .

Ce que j'essaie de décrire ici est très similaire aux fonctions Tagged du Javascript, et nous pourrions discuter si le compilateur doit accepter les fonctions utilisateur pour formater la chaîne ex :

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

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

@rodcorsi Si je lis correctement votre suggestion, cela nécessite de construire le formatage fmt.Printf dans le langage proprement dit, car le compilateur devra comprendre où commence et se termine %20s . C'est une des choses que j'essayais d'éviter.

Notez également que ma suggestion n'est pas du tout liée au formatage fmt.Printf et peut également être utilisée pour d'autres types d'interpolation.

Je serais opposé à traiter m"..." comme une extension à un appel de fonction, car cela masque ce qui se passe réellement et ajoute ce qui est en fait une deuxième syntaxe pour les appels de fonction. Il semble généralement raisonnable de passer une représentation plus structurée qu'une carte, pour éviter d'avoir besoin de réimplémentations correspondantes du comportement d'analyse partout. Peut-être une structure simple avec une tranche de sections de chaînes constantes, une tranche de chaînes pour les éléments entre accolades et une tranche d'interface ?

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

Les deuxième et troisième tranches doivent être de la même longueur et la première doit être plus longue. Il existe d'autres façons de représenter cela ainsi que d'encoder structurellement cette contrainte.
L'avantage que cela a sur un format qui expose directement la chaîne d'origine est qu'il y a une exigence plus lâche d'avoir un analyseur correct et performant dans la fonction qui la consomme. S'il n'y a pas de support pour les caractères échappés ou les chaînes m imbriquées, ce n'est probablement pas un gros problème, mais je préfère ne pas avoir besoin de réimplémenter et de tester cet analyseur, et la mise en cache de son résultat pourrait provoquer des fuites de mémoire d'exécution.

Si les "options de formatage" sont un désir fréquent des choses utilisant cette syntaxe, je pourrais voir qu'il y a une place pour elles dans la spécification, mais j'irais personnellement avec une syntaxe comme m"{name} is {age:%.2f} years old" où le compilateur passe juste tout après le : sur la fonction.

Bonjour, je voulais commenter ceci pour ajouter un soutien à cette proposition. J'ai travaillé avec de nombreux langages différents au cours des 5 dernières années (Kotlin, Scala, Java, Javascript, Python, Bash, un peu de C, etc.) et j'apprends Go maintenant.

Je pense que l'interpolation de chaînes est indispensable dans tout langage de programmation moderne, de la même manière que l'inférence de type, et nous l'avons dans Go.

Pour ceux qui soutiennent que vous pouvez accomplir la même chose avec Sprintf, alors, je ne comprends pas pourquoi nous avons l'inférence de type dans Go, vous pourriez accomplir la même chose en écrivant le type, n'est-ce pas ? Eh bien, oui, mais le point ici est que l'interpolation de chaîne réduit une grande partie de la verbosité dont vous avez besoin pour accomplir cela et est plus facile à lire (avec Sprintf, vous devez passer à la liste des arguments et à la chaîne d'avant en arrière pour donner un sens à la chaîne).

Dans les logiciels réels, il s'agit d'une fonctionnalité très appréciée.

C'est contre le design minimaliste de Go ? Non, ce n'est pas une fonctionnalité qui vous permet de faire des choses folles ou des abstractions qui compliquent votre code (comme l'héritage), c'est juste une façon d'écrire moins et d'ajouter de la clarté lorsque vous lisez le code, ce qui, je pense, n'est pas contre ce que Go est essayer de faire (nous avons l'inférence de type, nous avons l'opérateur :=, etc.).

Formatage bien fait pour un langage avec typage statique

Haskell possède des bibliothèques et des extensions de langage pour l'interpolation de chaînes. Ce n'est pas un truc de type.

Cette page vous a été utile?
0 / 5 - 0 notes