Go: propuesta: interpolación de cadenas

Creado en 8 sept. 2019  ·  67Comentarios  ·  Fuente: golang/go

Introducción

Para los que no saben lo que es:

Rápido

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#
cadena nombre = "Marca";
var fecha = FechaHora.Ahora;

Console.WriteLine($"¡Hola, {nombre}! Hoy es {date.DayOfWeek}, es {date:HH:mm} ahora.");

# 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"


devuelve errores.Nuevo("archivo de configuración de apertura: \{err}") // devuelve errores.Nuevo("archivo de configuración de apertura: " + err.Error())


var estado fmt.Stringer

msg := "estado de salida: {status}" // msg := "estado de salida: " + status.String()


v := 123
res := "valor = {v}" // res := "valor = " + algunaIntToStringConversionFunc(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("valor={v:04.3}") // valor=0123.450


v := "valor"
fmt.Println("valor='{v:a50}'") // valor='<45 espacios>valor'

# Conversions

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

escriba la interfaz del formateador {
Cadena de formato (cadena de formato)
}
```

se puede introducir más tarde para tratar con las opciones de interpolación

Pros y contras sobre el formato tradicional

ventajas

  • Tipo de seguridad
  • Rendimiento (depende del compilador)
  • Compatibilidad con opciones de formato personalizadas para tipos definidos por el usuario

Contras

  • Complicación de un compilador
  • Se admiten menos métodos de formato (no %v (?), %T , etc.)
Go2 LanguageChange Proposal

Comentario más útil

Para mí, la verificación de tipos como razón para incluir la interpolación de cadenas en el lenguaje no es tan convincente. Hay una razón más importante, que no depende del orden en que escribes las variables en fmt.Printf .

Tomemos uno de los ejemplos de la descripción de la propuesta y escríbalo en Ir con y sin interpolación de cadenas:

  • Sin interpolación de cadenas (Go actual)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
  • Funcionalidad equivalente con interpolación de cadenas (y mezclando el comentario de @bradfitz , necesario para expresar las opciones de formato)
name := "Mark"
date := time.Now()

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

Para mí, la segunda versión es más fácil de leer y modificar y menos propensa a errores, ya que no dependemos del orden en que escribimos las variables.

Al leer la primera versión, debe volver a leer la línea varias veces para verificar que sea correcta, moviendo los ojos hacia adelante y hacia atrás para crear un mapa mental entre la posición de un "%v" y el lugar donde debe colocar La variable.

He estado solucionando errores en varias aplicaciones (no escritas en Go, pero con el mismo problema) donde las consultas de la base de datos se habían escrito con muchos '?' en lugar de parámetros con nombre ( SELECT * FROM ? WHERE ? = ? AND ? != false ... ) y otras modificaciones (incluso sin darse cuenta durante una combinación de git) invirtió el orden de dos variables 😩

Entonces, para un lenguaje cuyo objetivo es facilitar el mantenimiento a largo plazo, creo que esta razón hace que valga la pena considerar la interpolación de cadenas.

Con respecto a la complejidad de esto: no conozco las partes internas del compilador Go, así que tome mi opinión con pinzas, pero _¿No podría simplemente traducir la segunda versión que se muestra en el ejemplo anterior a la primera?_

Todos 67 comentarios

¿Por qué no podemos usar solo ${...}? Swift ya tiene una sintaxis, JavaScript y Kotlin otra, C# otra más, también Perl... ¿Por qué inventar una variación más? ¿No sería mejor ceñirse al más legible y ya existente?

¿Por qué no podemos usar solo ${...}? Swift ya tiene una sintaxis, JavaScript y Kotlin otra, C# otra más, también Perl... ¿Por qué inventar una variación más? ¿No sería mejor ceñirse al más legible y ya existente?

Porque es posible que tengamos cadenas existentes con ${…} . Yo lo hago , por ejemplo.

Y \{ no está permitido en este momento.

Esto no parece tener una gran ventaja sobre llamar a fmt.Sprintf .

Esto no parece tener una gran ventaja sobre llamar a fmt.Sprintf .

Sí, además de seguridad de tipo completo, mejor rendimiento y facilidad de uso. Formato bien hecho para un idioma con escritura estática.

Por lo que puedo decir, esta propuesta no es más segura que usar fmt.Sprintf con %v . Es esencialmente idéntico con respecto a la seguridad de tipos. Estoy de acuerdo en que una implementación cuidadosa podría tener un mejor rendimiento en muchos casos. Soy agnóstico en la facilidad de uso.

Para implementar esto, tendríamos que escribir, en la especificación del idioma, el formato exacto que se usará para todos los tipos. Tendríamos que decidir y documentar cómo formatear un segmento, una matriz, un mapa. una interfaz Un canal. Esta sería una adición significativa a la especificación del idioma.

Creo que es una de esas preguntas en las que ambos caminos tienen derecho a existir, y solo depende de los que toman las decisiones decidir. En Go, la decisión ya se tomó, hace bastante tiempo, y funciona, y es idiomática. Eso es fmt.Sprintf con %v .

Históricamente, existen lenguajes en los que la interpolación dentro de una cadena ha estado presente desde el principio. Cabe destacar que es Perl. Esa es una de las razones por las que Perl se volvió tan popular, porque era súper conveniente en comparación con sprintf() en C et al. Y el %v aún no se había inventado. Y luego hay idiomas donde la interpolación estaba presente, pero era inconveniente sintácticamente, piense en "text" + v1 + "text" . Luego, JavaScript introdujo literales entrecomillados, que son de varias líneas y admiten la interpolación de expresiones arbitrarias dentro ${...} , lo que supuso una gran mejora en comparación con "text" + v1 + "text" . El Go también tiene literales de varias líneas de backtick, pero sin ${...} . Quién copió a quién no lo sé.

No estoy de acuerdo con @ianlancetaylor en que el apoyo a esto requerirá un esfuerzo sustancial. De hecho, fmt.Sprintf con %v hace exactamente esto, ¿no? Me parece una envoltura de sintaxis diferente a exactamente lo mismo debajo del capó. ¿Tengo razón?

Pero __estoy de acuerdo__ con @ianlancetaylor en que usar fmt.Sprintf con %v es igual de conveniente. También tiene exactamente la misma longitud en la pantalla y, lo que es muy importante en mi humilde opinión, ya es idiomático para Go. De alguna manera hace Go Go. Si copiamos e implementamos todas las demás características de todos los demás idiomas, entonces ya no será Go, sino todos los demás idiomas.

Hay una cosa mas. Desde mi larga experiencia con Perl, donde la interpolación de cadenas estuvo presente desde el principio, puedo decir que no es tan perfecto. Hay un problema con eso. Para programas simples y triviales, y probablemente para el 90% de todos los programas, la interpolación de cadenas de variables funciona bien. Pero luego, de vez en cuando, obtienes var=1.99999999999 y quieres imprimirlo como 1.99 y __no puedes__ hacer eso con la interpolación de cadenas estándar. O necesita hacer alguna conversión de antemano, o... Busque en los documentos y vuelva a aprender la sintaxis sprintf() olvidada hace mucho tiempo. Aquí __es__ el problema: el uso de la interpolación de cadenas le permite olvidar cómo usar la sintaxis similar a sprintf(), y probablemente sea la existencia misma. Y luego, cuando es necesario, dedica demasiado tiempo y esfuerzo a hacer las cosas más simples. Estoy hablando en el contexto de Perl, y fue una decisión de los diseñadores del lenguaje hacerlo.

Pero en Go se ha tomado una decisión diferente. El fmt.Sprintf con %v ya está aquí, es __tan conveniente, tan corto__ como las cadenas interpoladas, y es __idiomático__, ya que está en los documentos, en ejemplos, en todas partes. Y no sufre el problema de olvidar eventualmente cómo imprimir 1.99999999999 como 1.99.

La introducción de la sintaxis propuesta hará que Go se parezca un poco más a Swift y/o más a JavaScript, y a algunos les puede gustar eso. Pero creo que esta sintaxis particular no lo hará mejor, si no un poco peor.

Creo que la forma actual de imprimir las cosas debería permanecer como está. Y si alguien necesita más, hay plantillas para eso.

Si parte del argumento aquí es la seguridad en tiempo de compilación, no estoy de acuerdo en que sea un argumento convincente; go vet y, por extensión go test , han estado marcando usos incorrectos de fmt.Sprintf durante un tiempo.

También es posible optimizar el rendimiento hoy a través go generate , si realmente desea hacerlo. Es una compensación que no vale la pena la mayor parte del tiempo. Siento que lo mismo se aplica a la gran expansión de la especificación; la compensación generalmente no vale la pena.

Permitir llamadas a funciones dentro de cadenas interpoladas sería desafortunado, demasiado fácil de pasar por alto.
No permitirlos es otro caso especial innecesario.

Si parte del argumento aquí es la seguridad en tiempo de compilación, no estoy de acuerdo en que sea un argumento convincente; go vet y, por extensión, go test, han estado señalando usos incorrectos de fmt.Sprintf durante un tiempo. También es posible optimizar el rendimiento hoy a través de generar, si realmente desea hacerlo. Es una compensación que no vale la pena la mayor parte del tiempo. Siento que lo mismo se aplica a la gran expansión de la especificación; la compensación generalmente no vale la pena.

Pero la seguridad de los tipos debe estar garantizada por el compilador, no por las herramientas. Esto es semántica; es como decir que debería haber una herramienta para verificar dónde olvidó anular la verificación en lugar de tener opciones o valores declarables explícitamente anulables.

Aparte de eso, la única forma de que esto sea seguro es con tipos dependientes. La interpolación de cadenas es simplemente más azúcar sintáctico para las mismas cosas que fmt.Sprintf y, aunque estoy totalmente a favor de un buen azúcar, toda la comunidad go parece no estarlo.

O tal vez algo así como modificar el idioma para que funcione mejor con fmt.Printf y amigos.

Por ejemplo, si fmt admitiera algo como %(foo)v o %(bar)q , digamos que si se usa un literal de cadena que contiene %(<ident>) en una llamada a una función/método variable, entonces todas las referencias los símbolos se añaden a la lista de variantes automáticamente.

por ejemplo, este código:

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

realmente compilaría a:

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

Y fmt podría saltarse los bits innecesarios (name) y (age) .

Sin embargo, ese es un cambio de lenguaje de caso bastante especial.

Para mí, la verificación de tipos como razón para incluir la interpolación de cadenas en el lenguaje no es tan convincente. Hay una razón más importante, que no depende del orden en que escribes las variables en fmt.Printf .

Tomemos uno de los ejemplos de la descripción de la propuesta y escríbalo en Ir con y sin interpolación de cadenas:

  • Sin interpolación de cadenas (Go actual)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
  • Funcionalidad equivalente con interpolación de cadenas (y mezclando el comentario de @bradfitz , necesario para expresar las opciones de formato)
name := "Mark"
date := time.Now()

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

Para mí, la segunda versión es más fácil de leer y modificar y menos propensa a errores, ya que no dependemos del orden en que escribimos las variables.

Al leer la primera versión, debe volver a leer la línea varias veces para verificar que sea correcta, moviendo los ojos hacia adelante y hacia atrás para crear un mapa mental entre la posición de un "%v" y el lugar donde debe colocar La variable.

He estado solucionando errores en varias aplicaciones (no escritas en Go, pero con el mismo problema) donde las consultas de la base de datos se habían escrito con muchos '?' en lugar de parámetros con nombre ( SELECT * FROM ? WHERE ? = ? AND ? != false ... ) y otras modificaciones (incluso sin darse cuenta durante una combinación de git) invirtió el orden de dos variables 😩

Entonces, para un lenguaje cuyo objetivo es facilitar el mantenimiento a largo plazo, creo que esta razón hace que valga la pena considerar la interpolación de cadenas.

Con respecto a la complejidad de esto: no conozco las partes internas del compilador Go, así que tome mi opinión con pinzas, pero _¿No podría simplemente traducir la segunda versión que se muestra en el ejemplo anterior a la primera?_

@ianlancetaylor señaló que mi boceto anterior (https://github.com/golang/go/issues/34174#issuecomment-532416737) no es estrictamente compatible con versiones anteriores, ya que podría haber programas raros en los que esto cambiaría su comportamiento.

Una variación compatible con versiones anteriores sería agregar un nuevo tipo de "literal de cadena formateada", con el prefijo, digamos, f :

por ejemplo, este código:

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

realmente compilaría a:

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

Pero entonces el doble f (uno en Printf seguido de f antes del nuevo tipo de cadena literal) sería tartamudo.

Tampoco entiendo el funcionamiento interno del compilador, por lo que (quizás tontamente) también asumo que esto podría implementarse en el compilador, de modo que algo como
fmt.printf("Hello %s{name}. You are %d{age}")

compilaría a su formulación actual equivalente.

La interpolación de cadenas tiene el beneficio obvio de una mayor legibilidad (una decisión de diseño central de Go) y también se escala mejor a medida que las cadenas con las que uno trata se vuelven más largas y complicadas (otra decisión de diseño central de Go). Tenga en cuenta también que el uso de {age} le da a la cadena un contexto que de otro modo no tendría si solo estuviera leyendo la cadena por encima (y, por supuesto, ignorando el tipo que se especificó), la cadena podría haber terminado "Eres alto", "Estás [en la ubicación XXX]", "Estás trabajando demasiado" y, a menos que pongas la energía mental para asignar el método de formato a cada instancia de interpolación, no es inmediatamente obvio qué debe ir allí. Al eliminar este obstáculo mental (ciertamente pequeño), el programador puede concentrarse en la lógica en lugar del código.

El compilador implementa la especificación del lenguaje. La especificación de idioma actualmente no dice nada sobre el paquete fmt. No tiene que hacerlo. Puede escribir programas grandes de Go que no usen el paquete fmt en absoluto. Agregar la documentación del paquete fmt a la especificación del idioma lo haría notablemente más grande, lo cual es otra forma de decir que hace que el idioma sea mucho más complejo.

Eso no hace que esta propuesta sea imposible de adoptar, pero es un gran costo y necesitamos un gran beneficio para compensar ese costo.

O necesitamos una forma de discutir la interpolación de cadenas sin involucrar al paquete fmt. Esto es bastante claro para valores de tipo cadena, o incluso de tipo []byte , pero mucho menos claro para valores de otros tipos.

No estoy a favor de esta propuesta en parte por lo que @IanLanceTaylor dijo anteriormente y en parte porque, cuando intenta interpolar expresiones complejas con opciones de formato, cualquier ventaja de legibilidad tiende a perderse.

También a veces se olvida que la capacidad de incluir argumentos variados en la familia de funciones fmt.Print (y Println ) ya permite una forma de interpolación. Podemos reproducir fácilmente algunos de los ejemplos citados anteriormente con el siguiente código que, en mi opinión, es igual de legible:

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

Otra razón para seguir agregando y __tener__ en el idioma: https://github.com/golang/go/issues/34403#issuecomment -542560071

Encontramos que los comentarios de @alanfo en https://github.com/golang/go/issues/34174#issuecomment -540995458 son convincentes: puede usar fmt.Sprint para hacer un tipo simple de interpolación de cadenas. La sintaxis es quizás menos conveniente, pero cualquier enfoque en esto requeriría un marcador especial para interpolar las variables en cualquier caso. Y, como se señaló, esto permite el formato arbitrario de los valores que se van a interpolar.

Como se señaló anteriormente, existe una forma de hacer esto aproximadamente que incluso permite formatear las variables individuales. Por lo tanto, esta es una probable disminución . Dejando abierto durante cuatro semanas para comentarios finales.

Regularmente me enfrento a la creación de bloques de texto con más de 50 variables para insertar. Más de 70 en algunos casos. Esto sería fácil de mantener con las cadenas f de Python (similar a C# mencionado anteriormente). Pero estoy manejando esto en Go en lugar de Python por varias razones. La configuración inicial de fmt.Sprintf para administrar estos bloques es... ok. Pero, Dios no lo quiera, tengo que corregir un error o modificar el texto de alguna manera que implique mover o eliminar los marcadores %anything y sus variables relacionadas con la posición. Y construir mapas manualmente para pasar a template o configurar os.Expand tampoco es una gran opción. Me quedo con la velocidad (de configuración) y la facilidad de mantenimiento de f-strings por encima fmt.Sprintf cualquier día de la semana. Y no, fmt.Sprint no sería muy beneficioso. Más fácil de configurar que fmt.Sprintf en este caso. Pero pierde gran parte de su significado visualmente porque saltas dentro y fuera de las cuerdas. "My {age} is not {quality} in this discussion" no salta dentro y fuera de las cadenas de la forma en que lo hace "My ", age, " is not ", quality, " in this discussion" . Especialmente en el transcurso de muchas decenas de referencias. Mover texto y referencias es simplemente copiar y pegar con f-strings. Las eliminaciones son simplemente seleccionar y eliminar. Porque siempre estás dentro de la cadena. Este no es el caso cuando se usa fmt.Sprint . Es muy fácil seleccionar accidentalmente (o necesariamente) comas que no sean cadenas o terminaciones de cadenas de comillas dobles y moverlas para romper el formato y requerir ediciones para _volver a colocarlo en su lugar_. fmt.Sprint y fmt.Sprintf en estos casos consume mucho más tiempo y es propenso a errores que cualquier cosa que se parezca a f-strings.

¡Eso suena como una tarea bastante horrible como sea que la hagas!

Si me enfrentara a algo así, entonces ciertamente estaría pensando en términos de text/template inicialmente o, si fuera demasiado incómodo poner mis variables en una estructura o mapa, probablemente preferiría fmt.Sprint a fmt.Sprintf pero organice el código de la siguiente manera:

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

Aunque eso ocuparía mucho espacio en la pantalla, sería relativamente fácil cambiar, eliminar o mover cosas sin demasiado riesgo de cometer un error.

Hay algunas bibliotecas de interpolación de cadenas, pero sin características de lenguaje como tipos de unión o genéricos, no son tan flexibles ni fluidas como en otros lenguajes:

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

Alternativamente, go.uber.org/yarpc/internal/interpolate

Una vez más, construir un mapa solo para poder usar template/text , una biblioteca de terceros o un sistema mínimo por os.Expand es excelente cuando no tiene 50 o más claves que necesita crear para las variables que ya existen para el resto del código en cuestión. Y el único argumento real en contra de esta función es "es posible que olvide cómo usar correctamente el formato de %" y es posible que deba pasar 2 minutos adicionales usando Google para buscar el formato para la única instancia en la que absolutamente necesita la eficiencia, la especificidad o lo que sea. fmt.Sprintf ? Por otro lado, tal vez casi siempre necesites la eficiencia, la especificidad o lo que sea de fmt.Sprintf y nunca olvides el formateo porque siempre lo usas. No veo el problema.

¿El otro argumento es que hace que el lenguaje sea más complejo? Sí, lo hace. Técnicamente, cualquier adición al lenguaje aumenta su complejidad. Y estoy totalmente a favor de no hacer el lenguaje más complejo por razones triviales. _REALMENTE_ me gustaría ver el uso de in para todas las formas en que se usa en Python. Pero me conformaría con simplemente reemplazar := range con in . Apenas he tocado Python en el último año y solo lo usé durante dos años antes. Pero nueve de cada diez veces escribo in primero y luego lo arreglo con := range . Pero no voy a pelear por eso. := range es suficiente y obvio para razonar sobre el código que ha escrito anteriormente. Pero las cuerdas f y su tipo son algo completamente diferente. Esa es la funcionalidad básica, la usabilidad y la mantenibilidad. Absolutamente debería ser parte de la especificación Go. Si hubiera alguna instalación para hacerlo como una biblioteca de terceros, haría la biblioteca y terminaría con ella. Pero que yo sepa, ni siquiera es factible sin una expansión adicional al lenguaje Go. Entonces, como mínimo, creo que la expansión del idioma debería estar en su lugar.

@runeimp Quiero dejar en claro que su sugerencia de que podríamos comenzar a admitir "My {age} is not {quality} in this discussion" no funciona. Esa es una cadena Go válida hoy, y si esperáramos interpolar age y quality eso rompería los programas Go existentes. Tendría que ser algo más como "My \{age} is not \{quality} in this discussion" . Y los problemas de formatear valores que no son cadenas permanecen.

Aprecio que @ianlancetaylor. Y para que quede claro, las cadenas f de Python son f"My {age} is not {quality} in this discussion" o F'My {age} is not {quality} in this discussion' o cualquier combinación de cualquier caso f y una comilla simple o doble. Como se mencionó, se parece mucho a cómo C# parece hacerlo. Además, aunque prefiero el estilo Python f-string o C#, aceptaré absolutamente _CUALQUIER COSA_ que facilite un uso similar. InterPolationACTIVATE"My @$@age###^&%$ is not @$@quality###^&%$ in this discussion"INTERpolationDEACTVATEandNullify sería aceptable. Cojo, pero aceptable. Y ni siquiera estoy bromeando. Tomaría esa excusa poco convincente para la interpolación de cadenas de más fmt.Sprintf cualquier día de la semana. Los beneficios de mantenimiento por sí solos lo justificarían.

Entiendo que ayudaría a tu caso particular.

En https://blog.golang.org/go2-here-we-come @griesemer escribió que cualquier cambio en el idioma debería

  1. abordar un tema importante para muchas personas,
  2. tener un impacto mínimo en todos los demás, y
  3. vienen con una solución clara y bien entendida.

¿Es este un tema importante para muchas personas?

No tengo idea, pero espero que lo sea. Creo que es una característica que no es importante para cualquiera que solo haya tenido que lidiar con 5 referencias o menos en una cadena corta. O siempre tuve que administrarlo con plantillas de todos modos debido a otros requisitos de especificaciones. Pero para aquellos de nosotros que lo usamos. Se extraña mucho cuando no está presente. Y para aquellos que nunca han tenido la opción (solo usaron C, C++, etc. antes de Go) puede ser muy difícil comprender los beneficios en un nivel puramente teórico, ya que los ejemplos en su experiencia probablemente se olviden rápidamente o solo recordados como los peores proyectos con los que han tenido que lidiar tratando de olvidar los puntos débiles. Dudo que alguna vez vea una respuesta mayoritaria con respecto al problema, a menos que la mayoría de los desarrolladores de Go provengan de lenguajes que realmente admiten la interpolación de cadenas de variables locales.

Es un problema con el que siempre me he encontrado desde que comencé como desarrollador web profesional hace más de 20 años. En la mayoría de esos casos, las plantillas eran comunes para el front-end y el back-end. Al final de esa parte de mi carrera, eventualmente trabajé en Ruby on Rails y me enamoré instantáneamente de la interpolación de cadenas "My #{age} is not #{quality} in this discussion" de Ruby. A pesar de que la sintaxis de libra-curly-brace me pareció muy extraña al principio. Cuando hice la transición a la ingeniería de integración, usaba principalmente Python 3 y me sentía mucho más feliz usando su nuevo sistema str.format() en lugar de sus antiguas cadenas con formato de %. Con ese harías algo como "My {} is not {} in this discussion".format(age, quality) . Por lo tanto, el uso de referencias agnósticas al menos el tipo no importó en el 90% de los casos de uso. Lo cual es más simple de comprender y solo se preocupa por el índice. También se nombran referencias como "My {age} is not {quality} in this discussion".format(age=my_age_var, quality=my_quality_var) . Ahora, si se hace referencia a la misma var 30 veces, solo tiene que especificarla una vez en los parámetros y es fácil realizar un seguimiento, copiar y pegar o eliminar. Los parámetros con nombre (como entrada) son otra característica de Python que extraño en Go. Pero puedo vivir sin él si es necesario. Pero me enamoré de nuevo de f-strings (introducido en Python 3.6) en el momento en que lo vi. La interpolación de cadenas siempre me ha facilitado la vida.

@runeimp ¿Podría publicar un ejemplo de una de estas más de 50 interpolaciones de cadenas variables (en cualquier idioma que las tenga)? Creo que podría ayudar a la discusión tener un ejemplo real del código en cuestión.

Bien, este no es el código final, es un ejemplo simple, tiene solo 50 referencias y los nombres de las variables se cambiaron para proteger a los inocentes.

Ir 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 ejemplo potencial de cuerdas F

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

¿Cuál de los dos le gustaría mantener y agregar, actualizar, eliminar todos los meses durante los próximos años?

¿Puedo elegir ninguno? Ambos me parecen horribles.

¿Qué pasa con su caso que no se puede hacer con fmt.Sprintf("%#v", dt) ? Es decir, ¿cuál es el requisito para realizar el pedido? ¿Formato exacto (por ejemplo = frente a : para separadores, %q frente a %v , ...)? ¿Campos sin imprimir? ¿Nuevas líneas?

¿Hay algún otro programa analizando la salida, o es para consumo humano?

¿Qué pasa con el uso de reflejo?

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

Ambas opciones son razonables para este ejemplo muy específico. Pero eso no va a funcionar para nada más complejo. O literalmente cualquier otra cosa. Como cuando la misma referencia se usa muchas veces en una cadena. O los valores no forman parte de una sola estructura o mapa, sino que se generan en el mismo ámbito. Esto es sólo un ejemplo cludged. Desafortunadamente no puedo mostrar ningún código real. Pero en realidad basta con mirar cualquier página web con cientos de palabras en ella. Abra la vista de fuente e imagine que más de 50 palabras deben ser dinámicas. Ahora imagine que esto no es para una página web, no necesita un sistema de plantillas por ningún otro motivo, pero para posiblemente resolver este problema, todas las variables ya están dentro del alcance y se usan en otras partes del código y/o en múltiples lugares. en este bloque de código, y este texto puede cambiar cada 3 o 4 semanas en formas a veces grandes y muy significativas. Esto puede ser parte de un sistema que _ocasionalmente_ necesita generar alguno o todos los archivos PDF, CSV, SQL INSERT, correo electrónico, una llamada API, etc.

Sin ejemplos del mundo real, es muy difícil saber si esta propuesta es la solución adecuada al problema. Tal vez la interpolación de cadenas sea realmente la solución, o tal vez nos estemos encontrando con el problema XY . Tal vez la solución correcta ya esté en el idioma en alguna parte, o la solución sea un cambio en el reflejo del paquete, o quizás en el texto/plantilla del paquete.

En primer lugar, dudo que este sea un tema importante para muchas personas. Esta es la única discusión seria que recuerdo haber visto al respecto y la votación de emoji no sugiere un apoyo abrumador. Las bibliotecas de terceros tampoco son exactamente abundantes.

En segundo lugar, vale la pena señalar que incluso fmt.Printf (y la familia) pueden generar argumentos repetidos:

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

En tercer lugar, no creo que sea necesariamente el caso de que las personas que han usado la interpolación de cadenas en otros idiomas quieran verlo en Go. Lo he usado antes en varios idiomas y, aunque está bien cuando desea interpolar variables simples, tan pronto como intente usar expresiones más complejas, llamadas a funciones, caracteres de bandera escapados, etc. y luego agregue detalles de formato a todo eso. (lo que inevitablemente significa un enfoque de estilo 'printf') todo puede convertirse rápidamente en un desastre ilegible.

Finalmente, me parece que, cuando tiene una gran cantidad de variables para interpolar, text/template es el mejor enfoque y quizás, por lo tanto, deberíamos buscar formas de hacer que sea más conveniente cuando las variables son no parte de una estructura bien definida en lugar de integrar algo de esta complejidad en el lenguaje mismo.

Alternativamente, cree 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()
}

o

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

Puedo entender profundamente la opinión de @ianlancetaylor . Sabemos que podemos vivir con fmt. Printf como siempre lo hemos hecho.

Al mismo tiempo, no puedo estar más de acuerdo con la opinión de @alanfo .

más fácil de leer y modificar y menos propenso a errores

Es muy importante para la programación robusta del sistema.

Creo que adoptar características/ideas de otros idiomas no es una vergüenza en absoluto cuando puede ahorrar mucho tiempo valioso a las personas. En realidad, muchas personas sufren problemas de interpolación de cadenas.
Esta es la razón por la que otros lenguajes modernos están adoptando la interpolación de cadenas.

No quiero que Go siga siendo un lenguaje un poco mejor que C.
Creo que Go v2 es una oportunidad de ser mucho mejor Go.

image

En mi humilde opinión, si se proporciona la función de interpolación de cadenas, la mayoría de nosotros optará por usarla. Podemos sentirlo.

PD.
Desafortunadamente, el caso de @runeimp es demasiado extremo. Puedo sentir el dolor de la circunstancia. Pero desdibuja las ideas de la propuesta. (Sin ofender)

@doortts sin ofender. Siempre aprecio la conversación honesta.

Sabía que publicar código real podría hacer mella en el argumento. Y también sabía que publicar una aproximación que necesita un poco de creatividad para expandirse mentalmente probablemente enturbiaría la discusión. Así que me arriesgué porque no espero que nadie más lo haga. Pero compartir el código real no es una opción y las horas que tomaría poner esto en blanco y negro con un ejemplo concreto, pero de lo contrario, para lo que no tengo ningún uso, simplemente no es algo para lo que tengo la energía en este momento. Ya sé muy bien que este argumento es cuesta arriba por las razones que ya expliqué anteriormente. Sé que no soy una de la docena de personas que ven el valor real de esta característica. Pero es casi seguro que la mayoría de ellos no están cerca de este hilo. Solo estoy aquí porque vi una publicación al respecto en Reddit. De lo contrario, desconocía casi por completo este proceso. He estado muy contento con la forma en que las cosas han ido avanzando con Go sin sentir la necesidad de intervenir en la conversación. Pero quería ayudar con esta discusión porque sabía que no habría muchas voces luchando por ella simplemente debido a la cantidad de inercia que hay que superar cada vez que se sugiere un cambio en el lenguaje. Creo que nunca antes había visto una oposición tan fuerte a los cambios en un idioma. Y eso hace que sea muy intimidante publicar en apoyo de algo nuevo. No estoy diciendo que ningún idioma deba aceptar todas las solicitudes de funciones nuevas sin revisión. Solo que la razón por la que puede parecer tranquilo en el lado del soporte no siempre es porque el deseo no está allí. Y con eso pido que cualquier persona interesada en esta función publique _algo_. Cualquier cosa , para que podamos ver algunos números reales en esto.

Creo que por ser las vacaciones, hay menos atención en general. La interpolación de cadenas es extremadamente útil y deseable desde el punto de vista del desarrollador (por eso está en tantos otros idiomas), pero parece que esta propuesta no está preparada para un cambio tan grande en este punto, que si se rechaza, no lo creo. significa que no vale la pena volver a visitarlo en el futuro. Los pequeños cambios incrementales que harían esto obvio aún no están implementados.

Resolver variables dinámicamente por nombre desde el alcance no es algo que creo que sea posible (¿aunque yaegi Eval parece hacerlo?) actualmente.

Creo que la publicación a la que hace referencia @runeimp es mi publicación en r/golang https://www.reddit.com/r/golang/comments/d1199a/why_is_there_no_equivalent_to_f_strings_in_python/
Donde hay un poco más de discusión.

Solo quería lanzar mi sombrero al ruedo y decir que la interpolación variable sería de gran ayuda para mí, viniendo del mundo de python, algo como fmt.sprintf hace que Go parezca muy torpe y horrible de leer/mantener.

Go es principalmente muy elegante en la forma en que hace que hacer las cosas que valora sea muy fácil y poderoso. La legibilidad y la mantenibilidad son algunos de los principios básicos de su filosofía y realmente no debería ser tan difícil leer una cadena y saber qué está pasando en ella. Hay una cantidad de gastos generales seriamente no trivial que viene con la comprensión de lo que se imprimirá mientras se tiene en cuenta qué variables se asignan a qué elementos en una lista al final de esa cadena. Simplemente no deberíamos necesitar mantener esa sobrecarga mental.

Si las personas sienten que fmt.sprintf es apropiado para ellos, se puede usar. No siento que tener ambos le reste valor a Go como lenguaje ya que la propuesta es indiscutiblemente más legible que el método actual, intuitiva y más fácil de mantener. Esto no debería necesitar ser justificado por interpolaciones de más de 50 variables, es una mejora con interpolaciones de incluso una sola variable.

es algo como

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

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

realmente una perspectiva dañina para el idioma? Estoy feliz de que se demuestre que estoy equivocado, pero honestamente solo veo ventajas en esta propuesta (o algo así), excepto por la incompatibilidad con versiones anteriores, que es donde algo como implementar esto en una nueva versión principal de Go tiene sentido.

¿Se podría considerar dejar esta discusión abierta por un conjunto más de 4 semanas, ya que una parte considerable de la comunidad estaba de vacaciones para el Día de Acción de Gracias, Navidad y Año Nuevo cuando se propuso el corte?

Ha habido mucha más discusión desde https://github.com/golang/go/issues/34174#issuecomment -558844640, así que voy a retirar esto del período de comentarios finales.

Me inclino a estar de acuerdo con @ randall77 en que todavía no he visto un ejemplo convincente aquí. @runeimp , gracias por publicar el código de ejemplo, pero parece difícil de leer y cambiar de cualquier manera. Como sugiere @egonelbre , si queremos que esto sea más fácil de mantener, el primer paso parece ser encontrar un enfoque completamente diferente.

@cyclingwithelephants fmt.sprintf("I am %T{age} years old") no está sobre la mesa aquí. El lenguaje no proporciona ningún mecanismo que fmt.Sprintf pueda usar para resolver age . Go es un lenguaje compilado y los nombres de las variables locales no están disponibles en el momento de la ejecución. Esto sería más aceptable si pudiéramos encontrar alguna manera de hacer que funcione, tal vez en la línea de la sugerencia anterior de @bradfitz .

Gracias @ianlancetaylor por levantar la etiqueta del período de comentario final. 😀

Creo que la idea de @bradfitz fue genial. Supongo que existen limitaciones potenciales sin importar qué sin un contexto variable local, pero aceptaría felizmente esas limitaciones por no tener interpolación de cadenas. Si bien tengo un gran respeto por las actualizaciones en el formato de porcentaje en Go (me encantan las adiciones de %q , %v y %#v ), ese paradigma es antiguo. El hecho de que sea venerable no significa que sea la mejor manera de hacer las cosas. Al igual que la forma en que Go maneja la compilación, y especialmente la compilación cruzada es _MUY_ mejor que cómo se hace con C o C++. Ahora bien, ¿Go simplemente oculta todas las opciones desagradables del compilador con valores predeterminados sensatos y es igual de feo bajo el capó? No lo sé específicamente, pero creo que ese es el caso. Y eso está bien. Eso es completamente aceptable para mí. No me importa qué ritual oscuro haga el compilador para que la función funcione. Solo sé que la característica hace la vida más fácil. Y me ha facilitado la vida como desarrollador en todos los lenguajes que he usado que lo admiten. Y siempre es un dolor en idiomas que no lo soportan. Es significativamente más fácil de recordar que cómo usar los más de 30 caracteres especiales en formato porcentual para el 98 % del formato de cadena que necesito. Y decir usar %v en lugar de "el formato de porcentaje correcto" no es la misma facilidad de uso para la creación ni siquiera la misma facilidad de mantenimiento.

Ahora que hay un poco más de tiempo, trabajaré en un ejemplo y veré si puedo encontrar algunos artículos que sean un poco más esclarecedores de lo que he sido para ayudar a ilustrar los beneficios significativos para la eficiencia del trabajo para aquellos de nosotros que nos ocupamos de la interfaz humana. , generación de documentos y códigos, y manipulación de cadenas de forma regular.

Aquí hay un pensamiento que puede conducir a algo implementable. Aunque no sé si es una buena idea.

Agregue un nuevo tipo de cadena m"str" (y quizás lo mismo con una cadena sin procesar). Este nuevo tipo de literal de cadena se evalúa como map[string]interface{} . Buscar la cadena vacía en el mapa le da el literal de la cadena en sí. El literal de cadena puede contener expresiones entre llaves. Una expresión entre llaves se evalúa como si no estuviera en el literal de cadena y el valor se almacena en el mapa siendo la clave la subcadena que aparece entre llaves.

Por ejemplo:

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

Esto se imprimirá

twice i is {i * 2}
2

Dentro del literal de cadena, las llaves se pueden escapar con una barra invertida para indicar una llave simple. Una llave sin comillas ni coincidencias es un error de compilación. También es un error de compilación si la expresión entre llaves no se puede compilar. La expresión debe evaluar exactamente un valor, pero por lo demás no tiene restricciones. La misma cadena entre llaves puede aparecer varias veces en el literal de la cadena; se evaluará tantas veces como aparezca, pero solo una de las evaluaciones se almacenará en el mapa (porque todas tendrán la misma clave). No se especifica exactamente cuál está almacenado (esto es importante si la expresión es una llamada de función).

Por sí mismo, este mecanismo es peculiar pero inútil. Su ventaja es que se puede especificar claramente y posiblemente no requiere adiciones excesivas al lenguaje.

El uso viene con funciones adicionales. La nueva función fmt.Printfm funcionará exactamente como fmt.Printf , pero el primer argumento no será un string sino un map[string]interface{} . El "" en el mapa será una cadena de formato. La cadena de formato admitirá, además de las cosas habituales % , un nuevo modificador {str} . El uso de este modificador significará que en lugar de usar un argumento para el valor, se buscará str en el mapa y se usará ese valor.

Por ejemplo:

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

imprimirá la cadena hi repartida a 20 espacios.

Naturalmente, existirá el fmt.Printm más simple que sustituirá a cada expresión entre llaves por el valor contenido impreso por fmt.Print .

Por ejemplo:

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

imprimirá

i: 1; j: 2

Problemas con este enfoque: el extraño uso de un prefijo m antes de un literal de cadena; el m duplicado en uso normal, uno antes y otro después del paréntesis; la inutilidad general de una cadena m cuando no se usa con una función que espera una.

Ventajas: no es demasiado difícil de especificar; admite interpolación simple y formateada; no se limita a las funciones de fmt, por lo que puede funcionar con plantillas o usos no previstos.

Si fuera un mapa escrito (por ejemplo, runtime.StringMap), entonces podríamos usar fmt.Print e Println sin agregar el tartamudeo Printfm

Usar un tipo definido es una buena idea, pero no ayudaría con fmt.Printfm ; no pudimos usar el tipo definido como el primer argumento para fmt.Printf , ya que eso solo requiere string .

Una cosa que @runeimp mencionó anteriormente en el hilo pero que no se ha discutido completamente es 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.
}

Aunque esto es demasiado detallado para casos simples, es mucho más aceptable cuando tiene una gran cantidad de valores para interpolar (¡aunque cualquier enfoque tiene un problema con 70 valores!). Las ventajas incluyen: -

  1. Si usa un cierre para la función de mapeo, funciona bien con las variables locales.

  2. También trata bien con el formato arbitrario y lo mantiene fuera de la propia cadena interpolada.

  3. Si usa un marcador de posición en la cadena interpolada que no está presente en la función de mapeo, se reemplaza automáticamente con una cadena vacía.

  4. Los cambios en la función de mapeo son relativamente fáciles de realizar.

  5. Ya lo tenemos, no es necesario cambiar el idioma ni la biblioteca.

@ianlancetaylor , esa solución me parece una opción sólida. Aunque no veo por qué necesitamos métodos de impresión alternativos. Es probable que esté pasando por alto algo, pero parece un simple cambio de firma usando interface{} y verificación de tipo. Bien, me acabo de dar cuenta de que el cambio de firma podría resultar muy problemático para algún código existente. Pero si se implementó el mecanismo básico y también creamos un tipo stringlit que representa string o m"str" o si m"str" también se acepta string ¿sería ese un cambio importante aceptable para Go v2? Ambos son "literales de cadena", es solo que uno de ellos esencialmente tiene una bandera que permite una funcionalidad adicional, ¿no?

Gracias por mencionar eso nuevamente @alanfo , todos esos son puntos excelentes. 😃

He usado os.Expand para plantillas ligeras y puede ser muy útil en situaciones en las que necesita crear un mapa de valores de todos modos. Pero si el mapa no es necesario, y necesitaría hacer su cierre en varias áreas diferentes solo para capturar las variables locales para su función de reemplazo (ahora copiada muchas veces) ignora DRY por completo y puede conducir a problemas de mantenimiento y solo agrega más trabajo en el que las cadenas interpoladas "simplemente funcionarían", aliviarían esos problemas de mantenimiento y no requerirían la construcción de un mapa solo para administrar esa cadena dinámica.

@runeimp No podemos cambiar la firma de fmt.Printf . Eso rompería la compatibilidad con Go 1.

La noción de un tipo stringlit implica cambiar el sistema de tipos de Go, que es mucho más importante. Go intencionalmente tiene un sistema de tipos muy simple. No creo que queramos complicarlo para esta característica. E incluso si lo hiciéramos, fmt.Printf todavía tomaría un argumento string , y no podemos cambiar eso sin romper los programas existentes.

@ianlancetaylor Gracias por la aclaración. Agradezco el deseo de no romper la retrocompatibilidad con algo tan fundamental como el paquete fmt o el sistema de tipos. Solo esperaba que pudiera haber alguna posibilidad oculta (para mí) que podría ser una opción en ese sentido de alguna manera. 👼

Realmente me gusta la forma en que Ian implementa esto. ¿No ayudarían los genéricos con el problema 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 esta manera, se debe preservar la compatibilidad con Go 1.

Para la compatibilidad con Go 1, no podemos cambiar el tipo de una función en absoluto. Las funciones no solo se llaman. También se utilizan en código como

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

La gente escribe código como este cuando crea tablas de funciones o cuando usa la inyección de dependencia manual para las pruebas.

Tengo la sensación de que strings.Replacer (https://golang.org/pkg/strings/#Replacer) casi puede hacer una interpolación de cadenas, solo falta el identificador de interpolación (por ejemplo, ${...}) y el procesamiento de patrones (por ejemplo, si var i int = 2 , "${i+1}" debe asignarse a "3" en el reemplazo)

Otro enfoque más tendría una función integrada, por ejemplo, format("Soy un %(foo)s %(bar)d") que se expande a fmt.Sprintf("Soy un %s %d", foo, bar). Al menos, eso es totalmente compatible con versiones anteriores, FWIW.

Desde la perspectiva del diseño del lenguaje, sería peculiar que una función integrada se expandiera a una referencia a una función en la biblioteca estándar. Para proporcionar una definición clara para todas las implementaciones del idioma, la especificación del idioma tendría que definir completamente el comportamiento de fmt.Sprintf . Lo cual creo que queremos evitar.

Esto probablemente no hará felices a todos, pero creo que lo siguiente sería lo más general. se divide en tres partes

  1. funciones fmt.Printm que toman una cadena de formato y un map[string]interface{}
  2. acepta #12854 para que puedas soltar el map[string]interface{} al llamarlo
  3. permitir nombres sin clave en los literales del mapa como abreviatura de "name": name, o "qual.name": qual.name,

En conjunto, eso permitiría algo como

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

Eso todavía tiene la duplicación entre la cadena de formato y los argumentos, pero es mucho más ligero en la página y es un patrón que se automatiza fácilmente: un editor o herramienta podría completar automáticamente los {i, j} en función de la cadena y el compilador. le dejaría saber si no están en el alcance.

Eso no le permite hacer cálculos dentro de la cadena de formato, lo que puede ser bueno, pero lo he visto exagerado las suficientes veces como para considerarlo una ventaja.

Dado que se aplica a literales de mapa en general, se puede usar en otros casos. A menudo nombro mis variables después de la clave que estarán en el mapa que estoy construyendo.

Una desventaja de esto es que no se puede aplicar a las estructuras, ya que se pueden descifrar. Eso podría rectificarse requiriendo un : antes del nombre como {:i, :j} y luego podría hacer

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

¿Necesitamos algún soporte de idioma para esto? Tal como está ahora, puede verse así, ya sea con un tipo de mapa o con una API fluida y más segura:

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, todavía es ineficiente, pero parece que no es mucho trabajo hacer un paquete que soporte esto. Como beneficio adicional, incluí una API fluida diferente que podría decirse que también simula interpolaciones.

La propuesta de "cadena de mapa" de @ianlancetaylor (aunque personalmente prefiero "cadena de valor/variable" con sintaxis av"...") también permite casos de uso sin formato. Por ejemplo, #27605 (funciones de sobrecarga del operador) existe en gran medida porque hoy en día es difícil hacer una API legible para math/big y otras bibliotecas numéricas. Esta propuesta permitiría la función

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

Usado como

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

Es importante destacar que esta función auxiliar puede coexistir con la API de mayor rendimiento y potencia que existe actualmente.

Este enfoque permite que la biblioteca realice las optimizaciones que desee para expresiones grandes y también podría ser un patrón útil para otros DSL, ya que permite el análisis de expresiones personalizadas mientras sigue representando valores como variables de Go. En particular, estos casos de uso no son compatibles con las cadenas f de Python porque el propio lenguaje impone la interpretación de los valores incluidos.

@HALtheWise Gracias, eso es bastante bueno.

Quería comentar para mostrar un poco de apoyo a esta propuesta, desde la postura de un desarrollador general. He estado programando con golang durante más de 3 años profesionalmente. Cuando me mudé a golang (desde obj-c/swift) me decepcionó que no se incluyera la interpolación de cadenas. He usado C y C ++ durante más de una década en el pasado, por lo que printf no necesita un ajuste particular, aparte de tener ganas de retroceder un poco; descubrí que sí marca la diferencia con el mantenimiento del código y la legibilidad. para cadenas más complejas. Recientemente hice un poco de kotlin (para un sistema de compilación gradle), y usar la interpolación de cadenas fue una bocanada de aire fresco.

Creo que la interpolación de cadenas puede hacer que la composición de cadenas sea más accesible para aquellos que son nuevos en el idioma. También es una victoria para la UX técnica y el mantenimiento, debido a la reducción de la carga cognitiva tanto al leer como al escribir código.

Me alegro de que esta propuesta esté recibiendo una consideración real. Quedo a la espera de la resolución de la propuesta. =)

Si entendí bien, la propuesta de @ianlancetaylor es:

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

Después de eso, una función de impresión manejará ese mapa, analizará toda la plantilla nuevamente y aprovechará algunas ventajas de la plantilla analizada previamente.

¿Pero si expandimos m"str" ​​a una función?

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
)

Esta función tiene la siguiente firma:

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

Esta función funcionará mejor porque, para aprovechar más la plantilla analizada previamente, se podrían realizar muchas más optimizaciones de manera similar a como lo hace Rust con la función println! .

Lo que estoy tratando de describir aquí es muy similar a las funciones etiquetadas de Javascript, y podríamos discutir si el compilador debería aceptar funciones de usuario para formatear cadenas, por ejemplo:

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

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

@rodcorsi Si estoy leyendo su sugerencia correctamente, requiere crear el formato fmt.Printf en el idioma adecuado, porque el compilador tendrá que entender dónde comienza y termina %20s . Esa es una de las cosas que estaba tratando de evitar.

También tenga en cuenta que mi sugerencia no está ligada en absoluto al formato fmt.Printf , y también puede usarse para otros tipos de interpolación.

Me opondría a tratar m"..." como una expansión a una llamada de función, porque oscurece lo que realmente está sucediendo y agrega lo que es efectivamente una segunda sintaxis para las llamadas de función. En general, parece razonable pasar una representación más estructurada que un mapa, para evitar la necesidad de reimplementaciones coincidentes del comportamiento de análisis en todas partes. ¿Quizás una estructura simple con una porción de secciones de cadenas constantes, una porción de cadenas para las cosas entre llaves y una porción de interfaz?

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

La segunda y la tercera rebanada deben tener la misma longitud, y la primera debe ser una más larga. Hay otras formas de representar esto también para codificar esa restricción estructuralmente.
La ventaja que esto tiene sobre un formato que expone directamente la cadena original es que hay un requisito más flexible para tener un analizador correcto y eficaz en la función que lo consume. Si no hay soporte para caracteres escapados o m-strings anidados, probablemente no sea un gran problema, pero preferiría no tener que volver a implementar y probar ese analizador, y almacenar en caché su resultado podría causar pérdidas de memoria en tiempo de ejecución.

Si las "opciones de formato" son un deseo frecuente de las cosas que usan esta sintaxis, podría ver que hay un lugar para ellas en la especificación, pero personalmente iría con una sintaxis como m"{name} is {age:%.2f} years old" donde el compilador simplemente pasa todo después de : en la función.

Hola queria comentar esto para sumar apoyo a esta propuesta. He estado trabajando con muchos lenguajes diferentes en los últimos 5 años (Kotlin, Scala, Java, Javascript, Python, Bash, algo de C, etc.) y ahora estoy aprendiendo Go.

Creo que la interpolación de cadenas es imprescindible en cualquier lenguaje de programación moderno, de la misma manera que lo es la inferencia de tipos, y lo tenemos en Go.

Para aquellos que argumentan que pueden lograr lo mismo con Sprintf, entonces, no entiendo por qué tenemos inferencia de tipos en Go, podrían lograr lo mismo escribiendo el tipo, ¿verdad? Bueno, sí, pero el punto aquí es que la interpolación de cadenas reduce gran parte de la verbosidad que necesita para lograr eso y es más fácil de leer (con Sprintf tiene que saltar a la lista de argumentos y la cadena de un lado a otro para darle sentido). la cuerda).

En el software de la vida real, esta es una característica muy apreciada.

¿Está en contra del diseño minimalista de Go? No, no es una característica que te permita hacer locuras o abstracciones que compliquen tu código (como la herencia), es solo una forma de escribir menos y agregar claridad cuando lees el código, lo que creo que no va en contra de lo que es Go. tratando de hacer (tenemos inferencia de tipos, tenemos el operador :=, etc.).

Formato bien hecho para un idioma con escritura estática

Haskell tiene bibliotecas y extensiones de lenguaje para la interpolación de cadenas. No es una cosa de tipos.

¿Fue útil esta página
0 / 5 - 0 calificaciones