Go: proposta: interpolação de strings

Criado em 8 set. 2019  ·  67Comentários  ·  Fonte: golang/go

Introdução

Para quem não sabe o que é:

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#
nome da string = "Marca";
var data = DateTime.Now;

Console.WriteLine($"Olá, {name}! Hoje é {date.DayOfWeek}, agora é {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

variável := "var"
res := "123{variável}321" // res := "123" + variável + "321"


return errors.New("abrindo o arquivo de configuração: \{err}") // return errors.New("abrindo o arquivo de configuração: " + err.Error())


var status fmt.Stringer

msg := "status de saída: {status}" // msg := "status de saída: " + status.String()


v := 123
res := "valor = {v}" // res := "valor = " + 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

"{variável}"

Formatting also has formatting options. It may look like

"{variável[:]}"

#### Examples of options

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


v := "valor"
fmt.Println("value='{v:a50}'") // valor='<45 espaços>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

tipo interface do formatador {
Format(string de formato) string
}
```

pode ser introduzido mais tarde para lidar com opções de interpolação

Prós e contras sobre a formatação tradicional

Prós

  • Tipo de segurança
  • Desempenho (depende do compilador)
  • Suporte a opções de formatação personalizada para tipos definidos pelo usuário

Contras

  • Complicação de um compilador
  • Menos métodos de formatação suportados (sem %v (?), %T , etc)
Go2 LanguageChange Proposal

Comentários muito úteis

Para mim, a verificação de tipo como um motivo para incluir a interpolação de strings no idioma não é tão atraente. Há uma razão mais importante, que não depende da ordem em que você escreve as variáveis ​​em fmt.Printf .

Vamos pegar um dos exemplos da descrição da proposta e escrevê-lo em Go com e sem interpolação de strings:

  • Sem interpolação de string (Go atual)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
  • Funcionalidade equivalente com interpolação de strings (e misturando o comentário @bradfitz , necessário para expressar as opções de formatação)
name := "Mark"
date := time.Now()

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

Para mim, a segunda versão é mais fácil de ler e modificar e menos propensa a erros, pois não dependemos da ordem em que escrevemos as variáveis.

Ao ler a primeira versão, você precisa reler a linha várias vezes para verificar se está correta, movendo os olhos para frente e para trás para criar um mapeamento mental entre a posição de um "%v" e o local onde você precisa colocar a variável.

Eu tenho corrigido bugs em vários aplicativos (não escritos em Go, mas com o mesmo problema) onde as consultas de banco de dados foram escritas com muito '?' em vez de parâmetros nomeados ( SELECT * FROM ? WHERE ? = ? AND ? != false ... ) e outras modificações (mesmo inadvertidamente durante um git merge) inverteu a ordem de duas variáveis ​​😩

Portanto, para uma linguagem cujo objetivo é facilitar a manutenção a longo prazo, acho que esse motivo faz valer a pena considerar a interpolação de strings

Em relação à complexidade disso: eu não conheço os componentes internos do compilador Go, então leve minha opinião com um grão de sal, mas _não poderia apenas traduzir a segunda versão mostrada no exemplo acima para a primeira?_

Todos 67 comentários

Por que não podemos usar apenas ${...}? Swift já tem uma sintaxe, JavaScript e Kotlin outra, C# ainda outra, também Perl... Por que inventar mais uma variação? Não seria melhor ficar com o mais legível e já existente?

Por que não podemos usar apenas ${...}? Swift já tem uma sintaxe, JavaScript e Kotlin outra, C# ainda outra, também Perl... Por que inventar mais uma variação? Não seria melhor ficar com o mais legível e já existente?

Porque podemos ter strings existentes com ${…} . Eu , por exemplo.

E \{ não é permitido agora.

Isso não parece ter uma grande vantagem sobre pagar fmt.Sprintf .

Isso não parece ter uma grande vantagem sobre pagar fmt.Sprintf .

Sim, além de segurança total, melhor desempenho e facilidade de uso. Formatação correta para um idioma com digitação estática.

Tanto quanto posso dizer, esta proposta não é mais segura do que usar fmt.Sprintf com %v . É essencialmente idêntico no que diz respeito à segurança do tipo. Concordo que uma implementação cuidadosa poderia ter melhor desempenho em muitos casos. Eu sou agnóstico em facilidade de uso.

Para implementar isso, teríamos que escrever, na especificação da linguagem, a formatação exata a ser usada para todos os tipos. Teríamos que decidir e documentar como formatar uma fatia, um array, um mapa. Uma interface. Um canal. Isso seria uma adição significativa à especificação do idioma.

Eu acho que é uma daquelas questões em que ambas as formas têm o direito de existir, e cabe apenas aos tomadores de decisão decidirem. Em Go, a decisão já foi tomada, há muito tempo, e funciona, e é idiomática. Isso é fmt.Sprintf com %v .

Historicamente, existem linguagens em que a interpolação dentro de uma string está presente desde o início. Notavelmente é Perl. Essa é uma das razões pelas quais Perl se tornou tão popular, porque era super conveniente em comparação com sprintf() em C et al. E o %v ainda não havia sido inventado. E depois há idiomas onde a interpolação estava presente, mas era inconveniente sintaticamente, pense em "text" + v1 + "text" . JavaScript, em seguida, introduziu literais com aspas invertidas, que são de várias linhas, e suportam a interpolação de expressões arbitrárias dentro de ${...} , o que foi uma grande melhoria em comparação com "text" + v1 + "text" . O Go também tem literais de várias linhas de backtick, mas sem ${...} . Quem copiou quem eu não sei.

Não concordo com @ianlancetaylor que o suporte a isso exigirá um esforço substancial. Na verdade, fmt.Sprintf com %v faz exatamente isso, não é? Parece-me um invólucro de sintaxe diferente para exatamente a mesma coisa sob o capô. Estou certo?

Mas __Eu concordo__ com @ianlancetaylor que usar fmt.Sprintf com %v é tão conveniente. Também é exatamente o mesmo em comprimento na tela e, o que é IMHO muito importante - já é idiomático para Go. Isso meio que faz Go Go. Se copiarmos e implementarmos todos os outros recursos de todos os outros idiomas, não será mais Go, mas todos os outros idiomas.

Há mais uma coisa. Da minha longa experiência com Perl, onde a interpolação de strings estava presente desde o início, posso dizer que não é tão perfeito. Há um problema com isso. Para programas simples e triviais, e provavelmente para 90% de todos os programas, a interpolação de variáveis ​​de strings funciona bem. Mas então, de vez em quando, você obtém um var=1.99999999999 , e deseja imprimi-lo como 1.99 , e você __não pode__ fazer isso com a interpolação de string padrão. Você precisa fazer alguma conversão de antemão, ou... Olhe para os documentos, e reaprenda a sintaxe sprintf() há muito esquecida. Aqui __é__ o problema - usar interpolação de string permite que você esqueça como usar a sintaxe do tipo sprintf(), e provavelmente é a própria existência. E então, quando é necessário - você gasta muito tempo e esforço para fazer as coisas mais simples. Estou falando no contexto Perl, e foi uma decisão do(s) designer(es) de linguagem fazer isso.

Mas em Go, uma decisão diferente foi tomada. O fmt.Sprintf com %v já está aqui, é __tão conveniente, tão curto__ quanto strings interpoladas, e é __idiomático__, pois está nos documentos, nos exemplos, em todos os lugares. E não sofre do problema de eventualmente esquecer como imprimir 1.99999999999 como 1.99.

A introdução da sintaxe proposta tornará o Go um pouco mais parecido com o Swift e/ou mais com o JavaScript, e alguns podem gostar disso. Mas acho que essa sintaxe em particular não o tornará melhor, se não um pouco pior.

Eu acho que a maneira existente de imprimir as coisas deve ficar como está. E se alguém precisar de mais - existem modelos para isso.

Se parte do argumento aqui é segurança em tempo de compilação, não concordo que seja um argumento convincente; go vet , e por extensão go test , têm sinalizado usos incorretos de fmt.Sprintf por um tempo.

Também é possível otimizar o desempenho hoje via go generate , se você realmente deseja fazê-lo. É uma troca que não vale a pena na maioria das vezes. Eu sinto que o mesmo se aplica a expandir bastante a especificação; a troca geralmente não vale a pena.

Permitir chamadas de função dentro de strings interpoladas seria lamentável - muito fácil de perder.
Não permiti-los é outro caso especial desnecessário.

Se parte do argumento aqui é segurança em tempo de compilação, não concordo que seja um argumento convincente; go vet, e por extensão go test, têm sinalizado usos incorretos de fmt.Sprintf por um tempo. Também é possível otimizar o desempenho hoje via go generate, se você realmente deseja fazê-lo. É uma troca que não vale a pena na maioria das vezes. Eu sinto que o mesmo se aplica a expandir bastante a especificação; a troca geralmente não vale a pena.

Mas a segurança de tipo deve ser garantida pelo compilador, não pelo ferramental. Isso é semântica; é como dizer que deve haver uma ferramenta para verificar onde você esqueceu de verificar nulos em vez de ter opcionais ou valores anuláveis ​​explicitamente declarados.

Além disso - a única maneira de isso ser seguro é com tipos dependentes. A interpolação de strings é apenas mais açúcar sintático para as mesmas coisas que fmt.Sprintf e, embora eu seja a favor de um bom açúcar, toda a comunidade go parece não ser.

Ou talvez algo como modificar o idioma para funcionar melhor com fmt.Printf & amigos.

Como se fmt suportasse algo como %(foo)v ou %(bar)q , digamos que se uma string literal contendo %(<ident>) for usada em uma chamada para um func/método variadic, então todos os referenciados símbolos são anexados à lista variada automaticamente.

por exemplo, este código:

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

realmente compilaria para:

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

E fmt poderia simplesmente pular os bits desnecessários (name) e (age) .

Essa é uma mudança de linguagem de caso bastante especial, no entanto.

Para mim, a verificação de tipo como um motivo para incluir a interpolação de strings no idioma não é tão atraente. Há uma razão mais importante, que não depende da ordem em que você escreve as variáveis ​​em fmt.Printf .

Vamos pegar um dos exemplos da descrição da proposta e escrevê-lo em Go com e sem interpolação de strings:

  • Sem interpolação de string (Go atual)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
  • Funcionalidade equivalente com interpolação de strings (e misturando o comentário @bradfitz , necessário para expressar as opções de formatação)
name := "Mark"
date := time.Now()

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

Para mim, a segunda versão é mais fácil de ler e modificar e menos propensa a erros, pois não dependemos da ordem em que escrevemos as variáveis.

Ao ler a primeira versão, você precisa reler a linha várias vezes para verificar se está correta, movendo os olhos para frente e para trás para criar um mapeamento mental entre a posição de um "%v" e o local onde você precisa colocar a variável.

Eu tenho corrigido bugs em vários aplicativos (não escritos em Go, mas com o mesmo problema) onde as consultas de banco de dados foram escritas com muito '?' em vez de parâmetros nomeados ( SELECT * FROM ? WHERE ? = ? AND ? != false ... ) e outras modificações (mesmo inadvertidamente durante um git merge) inverteu a ordem de duas variáveis ​​😩

Portanto, para uma linguagem cujo objetivo é facilitar a manutenção a longo prazo, acho que esse motivo faz valer a pena considerar a interpolação de strings

Em relação à complexidade disso: eu não conheço os componentes internos do compilador Go, então leve minha opinião com um grão de sal, mas _não poderia apenas traduzir a segunda versão mostrada no exemplo acima para a primeira?_

@ianlancetaylor apontou que meu esboço acima (https://github.com/golang/go/issues/34174#issuecomment-532416737) não é estritamente compatível com versões anteriores, pois pode haver programas raros em que isso mudaria seu comportamento.

Uma variação compatível com versões anteriores seria adicionar um novo tipo de "literal de string formatado", prefixado por, digamos, um f :

por exemplo, este código:

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

realmente compilaria para:

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

Mas então o duplo f (um em Printf seguido pelo f antes do novo tipo de literal de string) seria gago.

Eu também não entendo o funcionamento interno do compilador, então eu (talvez tolamente) também assumo que isso poderia ser implementado no compilador, para que algo como
fmt.printf("Hello %s{name}. You are %d{age}")

compilaria para sua formulação atual equivalente.

A interpolação de strings tem o benefício óbvio de maior legibilidade (uma decisão central de design do Go) e também escala melhor à medida que as strings com as quais se lida se tornam mais longas e mais complicadas (outra decisão central de design do Go). Observe também que usar {age} fornece o contexto da string que, de outra forma, não teria se você estivesse apenas lendo a string (e, é claro, ignorando o tipo que foi especificado), a string poderia ter terminado "Você é alto", "Você está [no local XXX]", "Você está trabalhando demais" e, a menos que você coloque energia mental para mapear o método de formato para cada instância de interpolação, não é imediatamente óbvio o que deve ir lá. Ao remover esse obstáculo mental (reconhecidamente pequeno), o programador pode se concentrar na lógica em vez do código.

O compilador implementa a especificação da linguagem. A especificação do idioma atualmente não diz nada sobre o pacote fmt. Não precisa. Você pode escrever grandes programas Go que não usam o pacote fmt. Adicionar a documentação do pacote fmt à especificação da linguagem a tornaria visivelmente maior, o que é outra maneira de dizer que torna a linguagem muito mais complexa.

Isso não impossibilita a adoção dessa proposta, mas é um custo grande, e precisamos de um grande benefício para superar esse custo.

Ou precisamos de uma maneira de discutir a interpolação de strings sem envolver o pacote fmt. Isso é bastante claro para valores do tipo string, ou mesmo do tipo []byte , mas muito menos claro para valores de outros tipos.

Não sou a favor desta proposta em parte por causa do que @IanLanceTaylor disse acima e em parte porque, quando você tenta interpolar expressões complexas com opções de formatação, qualquer vantagem de legibilidade tende a sair pela janela.

Também às vezes é esquecido que a capacidade de incluir argumentos variáveis ​​na família de funções fmt.Print (e Println ) já permite uma forma de interpolação. Podemos reproduzir facilmente alguns dos exemplos citados anteriormente com o seguinte código que, a meu ver, é igualmente legível:

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

Outro motivo para ainda adicionar e __have__ no idioma: https://github.com/golang/go/issues/34403#issuecomment -542560071

Achamos os comentários de @alanfo em https://github.com/golang/go/issues/34174#issuecomment -540995458 convincentes: você pode usar fmt.Sprint para fazer um tipo simples de interpolação de strings. A sintaxe talvez seja menos conveniente, mas qualquer abordagem sobre isso exigiria um marcador especial para que as variáveis ​​fossem interpoladas em qualquer caso. E, conforme observado, isso permite a formatação arbitrária dos valores a serem interpolados.

Como observado acima, existe uma maneira de fazer isso aproximadamente que permite até mesmo a formatação das variáveis ​​individuais. Portanto, este é um declínio provável . Deixando aberto por quatro semanas para comentários finais.

Eu sou regularmente confrontado com a construção de blocos de texto com mais de 50 variáveis ​​para inserir. Mais de 70 em alguns casos. Isso seria fácil de manter com f-strings do Python (semelhante ao C# mencionado acima). Mas estou lidando com isso em Go em vez de Python por vários motivos. A configuração inicial de fmt.Sprintf para gerenciar esses blocos é... ok. Mas Deus me livre de corrigir um erro ou modificar o texto de qualquer maneira que envolva mover ou excluir marcadores %anything e suas variáveis ​​relacionadas à posição. E construir mapas manualmente para passar para template ou configurar os.Expand também não é uma ótima opção. Vou considerar a velocidade (de configuração) e a facilidade de manutenção das cordas f acima de fmt.Sprintf qualquer dia da semana. E não, fmt.Sprint não seria extremamente benéfico. Mais fácil de configurar do que fmt.Sprintf neste caso. Mas perde muito do seu significado visual porque você pula dentro e fora das cordas. "My {age} is not {quality} in this discussion" não pula dentro e fora das cordas do jeito que "My ", age, " is not ", quality, " in this discussion" faz. Especialmente ao longo de muitas dezenas de referências. Mover texto e referências é apenas copiar e colar com f-strings. As exclusões são apenas selecionar e excluir. Porque você está sempre dentro da corda. Este não é o caso ao usar fmt.Sprint . É muito fácil selecionar acidentalmente (ou necessariamente) vírgulas que não sejam string ou terminações de string com aspas duplas e movê-los para quebrar a formatação e exigir edições para _massageá-lo de volta ao lugar_. fmt.Sprint e fmt.Sprintf nesses casos é muito mais demorado e propenso a erros do que qualquer coisa parecida com f-strings.

Isso soa como uma tarefa muito horrível, no entanto, você faz isso!

Se eu fosse confrontado com algo assim, certamente estaria pensando em termos de text/template inicialmente ou, se fosse muito estranho colocar minhas variáveis ​​em uma estrutura ou mapa, provavelmente preferiria fmt.Sprint a fmt.Sprintf mas organize o código da seguinte forma:

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

Embora isso ocupasse muito espaço na tela, seria relativamente fácil alterar, excluir ou mover coisas sem muito risco de cometer um erro.

Existem algumas bibliotecas de interpolação de strings, mas sem recursos de linguagem, como tipos de união ou genéricos, elas não são tão flexíveis nem fluidas como em outras linguagens:

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

Como alternativa, go.uber.org/yarpc/internal/interpolate

Mais uma vez, construir um mapa apenas para que eu possa usar template/text , uma biblioteca de terceiros ou um sistema mínimo para os.Expand é ótimo quando você não tem 50 ou mais chaves que precisa criar para variáveis que já existem para o restante do código em questão. E o único argumento real contra esse recurso é "você pode esquecer como usar corretamente a formatação %" e pode precisar gastar 2 minutos extras usando o Google para pesquisar a formatação para a instância em que você precisa absolutamente da eficiência, especificidade ou qualquer outra coisa fmt.Sprintf ? Por outro lado, talvez você quase sempre precise da eficiência, especificidade ou qualquer outra coisa de fmt.Sprintf e nunca esqueça a formatação porque você sempre a usa. Eu não vejo o problema.

O outro argumento é que torna a linguagem mais complexa? Sim. Tecnicamente, qualquer adição à linguagem aumenta sua complexidade. E sou a favor de não tornar a linguagem mais complexa por razões triviais. Eu _REALLY_ gostaria de ver o uso de in para todas as maneiras que ele é usado em Python. Mas eu me contentaria em apenas substituir := range por in . Eu quase não toquei em Python no ano passado e só o usei por dois anos antes. Mas nove em cada dez vezes eu digito in primeiro e depois corrijo com := range . Mas não vou brigar por isso. := range é suficiente e óbvio para raciocinar sobre o código que você escreveu antes. Mas f-strings e seu tipo é algo completamente diferente. Essa é a funcionalidade, usabilidade e capacidade de manutenção do núcleo duro. Ele absolutamente deve fazer parte da especificação Go. Se houvesse alguma facilidade para fazê-lo como uma biblioteca de terceiros, eu faria a biblioteca e terminaria com ela. Mas, que eu saiba, não é viável sem uma expansão extra para a linguagem Go. Então, pelo menos, acho que a expansão para o idioma deve estar em vigor.

@runeimp Quero deixar claro que sua sugestão de que poderíamos começar a apoiar "My {age} is not {quality} in this discussion" não funciona. Essa é uma string Go válida hoje, e se esperássemos interpolar age e quality isso quebraria os programas Go existentes. Teria que ser algo mais como "My \{age} is not \{quality} in this discussion" . E os problemas de formatação de valores não string permanecem.

Eu aprecio que @ianlancetaylor. E só para ficar claro, as strings f do Python são f"My {age} is not {quality} in this discussion" ou F'My {age} is not {quality} in this discussion' ou qualquer combinação de qualquer caso f e aspas simples ou duplas. Como mencionado, muito parecido com como o C# parece fazer isso. Além disso, embora eu prefira o estilo f-string ou C# do Python, aceitarei absolutamente _ANYTHING_ que facilite um uso semelhante. InterPolationACTIVATE"My @$@age###^&%$ is not @$@quality###^&%$ in this discussion"INTERpolationDEACTVATEandNullify seria aceitável. Fraco, mas aceitável. E nem estou brincando. Eu aceitaria essa desculpa esfarrapada para interpolação de strings em fmt.Sprintf qualquer dia da semana. Os benefícios de manutenção por si só justificariam isso.

Entendo que ajudaria no seu caso particular.

Em https://blog.golang.org/go2-here-we-come @griesemer escreveu que qualquer mudança no idioma deveria

  1. abordar um assunto importante para muitas pessoas,
  2. ter um impacto mínimo sobre todos os outros, e
  3. vêm com uma solução clara e bem compreendida.

Esta é uma questão importante para muitas pessoas?

Eu não tenho idéia, mas eu esperava que fosse. Eu acho que é um recurso que não é importante para quem já teve que lidar com 5 referências ou menos em uma string curta. Ou sempre teve que gerenciá-lo com modelos de qualquer maneira por causa de outros requisitos de especificação. Mas para aqueles de nós que o usam. Faz muita falta quando não está presente. E para aqueles que nunca tiveram a opção (só usaram C, C++, etc. antes do Go) pode ser muito difícil entender os benefícios em um nível puramente teórico, pois os exemplos em sua experiência provavelmente são rapidamente esquecidos ou apenas lembrados como os piores projetos com os quais tiveram que lidar tentando esquecer os pontos problemáticos. Duvido que você veja uma resposta da maioria sobre o problema, a menos que a maioria dos desenvolvedores Go venha de linguagens que realmente suportam interpolação de string de variável local.

É um problema que sempre tive desde que comecei como desenvolvedor web profissional há mais de 20 anos. Na maioria desses casos, a modelagem era comum para front-end e back-end. No final dessa parte da minha carreira, eventualmente trabalhei em Ruby on Rails e me apaixonei instantaneamente pela interpolação de strings "My #{age} is not #{quality} in this discussion" do Ruby. Mesmo que a sintaxe de libra-curly-brace me tenha parecido muito estranha inicialmente. Quando fiz a transição para a engenharia de integração, eu estava usando principalmente o Python 3 e estava muito mais feliz usando o novo sistema str.format() em vez de suas strings formatadas em % antigas. Com esse você faria algo como "My {} is not {} in this discussion".format(age, quality) . Portanto, usar referências agnósticas pelo menos tipo não importava para 90% dos casos de uso. O que é mais simples de compreender e só se preocupa com o índice. Referências também nomeadas como "My {age} is not {quality} in this discussion".format(age=my_age_var, quality=my_quality_var) . Agora, se o mesmo var for referenciado 30 vezes, você só precisa especificá-lo uma vez nos parâmetros e é fácil acompanhar, copiar e colar ou excluir. Parâmetros nomeados (como entrada) é outro recurso do Python que sinto falta em Go. Mas eu posso viver sem ele se for necessário. Mas me apaixonei novamente por f-strings (introduzidas no Python 3.6) no momento em que coloquei os olhos nela. A interpolação de strings sempre facilitou minha vida.

@runeimp Você poderia postar um exemplo de uma dessas mais de 50 interpolações de strings variáveis ​​(em qualquer idioma que você as tenha)? Acho que pode ajudar a discussão ter um exemplo real do código em questão.

OK, este não é o código final, é um exemplo simples, possui apenas 50 referências, e os nomes das variáveis ​​foram alterados para proteger os 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,
    )
}

Um exemplo potencial de cordas 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}`,
    )
}

Qual dos dois você gostaria de manter e adicionar, atualizar, excluir todos os meses pelos próximos anos?

Não posso escolher nenhum? Ambos me parecem horríveis.

E o seu caso não pode ser feito com fmt.Sprintf("%#v", dt) ? Ou seja, qual é o requisito no pedido? Formatação exata (por exemplo = vs : para separadores, %q vs. %v , ...)? Campos não impressos? Novas linhas?

Algum outro programa está analisando a saída ou isso é para consumo humano?

Que tal usar refletir?

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 as opções são razoáveis ​​para este exemplo muito específico. Mas isso não vai voar para nada mais complexo. Ou literalmente qualquer outra coisa. Como quando a mesma referência é usada muitas vezes em uma string. Ou os valores não fazem parte de uma única estrutura ou mapa, mas são gerados no mesmo escopo. Este é apenas um exemplo agrupado. Infelizmente não posso mostrar nenhum código real. Mas realmente basta olhar para qualquer página da web com centenas de palavras. Abra a visualização de origem e imagine que mais de 50 palavras precisam ser dinâmicas. Agora imagine que isso não é para uma página da web, você não precisa de um sistema de templates por nenhum outro motivo, mas para possivelmente resolver esse problema, todas as variáveis ​​já estão no escopo sendo usadas em outras partes do código e/ou em vários lugares neste bloco de código, e este texto pode ser alterado a cada 3 a 4 semanas de maneiras às vezes grandes e muito significativas. Isso pode ser parte de um sistema que _ocasionalmente_ precisa gerar um ou todos os PDF, CSV, SQL INSERT, e-mail, uma chamada de API, etc.

Sem exemplos do mundo real, é realmente difícil dizer se esta proposta é a solução certa para o problema. Talvez a interpolação de strings seja realmente a solução, ou talvez estejamos enfrentando o problema XY . Talvez a solução certa já esteja no idioma em algum lugar, ou a solução seja uma mudança para refletir o pacote, ou talvez o texto/modelo do pacote.

Em primeiro lugar, duvido que esta seja uma questão importante para muitas pessoas. Esta é a única discussão séria que me lembro de ter visto sobre isso e a votação dos emojis não sugere um apoio esmagador. Bibliotecas de terceiros também não são exatamente abundantes.

Em segundo lugar, vale ressaltar que mesmo fmt.Printf (e family) podem fazer argumentos repetidos:

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

Em terceiro lugar, não acho que seja necessariamente o caso de pessoas que usaram interpolação de strings em outros idiomas quererem vê-la em Go. Eu usei isso antes em vários idiomas e, embora seja bom quando você quiser interpolar variáveis ​​simples, assim que você tentar usar expressões mais complexas, chamadas de função, caracteres de sinalizador de escape etc. e adicionar detalhes de formatação a tudo isso (o que inevitavelmente significa uma abordagem de estilo 'printf') a coisa toda pode rapidamente se tornar uma bagunça ilegível.

Finalmente, parece-me que, quando você tem um grande número de variáveis ​​para interpolar, então text/template é a melhor abordagem e talvez, portanto, devêssemos procurar maneiras de tornar isso mais conveniente de usar quando as variáveis ​​são não faz parte de uma estrutura bem definida ao invés de integrar algo dessa complexidade na própria linguagem.

Como alternativa, crie uma 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()
}

Posso entender profundamente a opinião de @ianlancetaylor . Sabemos que podemos viver com fmt. Printf como sempre fizemos.

Ao mesmo tempo, não poderia concordar mais com a opinião do @alanfo .

mais fácil de ler e modificar e menos propenso a erros

É muito importante para a programação robusta do sistema.

Acho que adotar recursos/ideias de outras linguagens não é uma vergonha quando pode economizar muito tempo precioso das pessoas. Na verdade, muitas pessoas estão sofrendo com problemas de interpolação de strings.
Existe a razão pela qual outras linguagens modernas estão adotando a interpolação de strings.

Eu não quero que Go continue sendo uma linguagem um pouco melhor que C.
Acho que Go v2 é uma chance de ser Go muito melhor.

image

IMHO, se o recurso de interpolação de strings for fornecido, a maioria de nós optará por usá-lo. Podemos senti-lo.

obs.
Infelizmente, o caso de @runeimp é um caso muito extremo. Posso sentir a dor da circunstância. Mas borra as ideias da proposta. (Sem ofensa)

@doortts sem ofensa. Eu sempre aprecio uma conversa honesta.

Eu sabia que postar código real poderia prejudicar o argumento. E eu também sabia que postar uma aproximação que precisa de um pouco de criatividade para expandir mentalmente provavelmente iria apenas atrapalhar a discussão. Então eu arrisquei porque não espero que mais ninguém faça isso. Mas compartilhar código real não é uma opção e as horas que levaria para colocar isso em preto e branco com um exemplo concreto, mas de outra forma para o qual não tenho uso zero, simplesmente não é algo para o qual tenho energia agora. Eu já sei bem que esse argumento é difícil por causa das razões que já afirmei anteriormente. Eu sei que não sou uma das dezenas de pessoas que veem o valor real desse recurso. Mas a maioria deles quase certamente não está nem perto deste tópico. Só estou aqui porque vi um post sobre isso no Reddit. Caso contrário, eu estava quase completamente inconsciente desse processo. Estou muito feliz com a forma como as coisas estão avançando com o Go sem sentir a necessidade de entrar na conversa. Mas eu queria ajudar nessa discussão porque sabia que não haveria muitas vozes lutando por ela simplesmente devido à quantidade de inércia a ser superada sempre que qualquer mudança na linguagem fosse sugerida. Acho que nunca vi uma oposição tão forte a mudanças em um idioma. E isso torna muito intimidante postar em apoio a algo novo. Não estou dizendo que nenhum idioma deve aceitar todas as solicitações de novos recursos sem revisão. Só que a razão pela qual pode parecer quieto no lado do suporte nem sempre é porque o desejo não está lá. E com isso peço que qualquer pessoa interessada neste recurso por favor poste _alguma coisa_. Qualquer coisa , para que possamos ver alguns números reais sobre isso.

Acho que por ser feriado, há menos atenção em geral. A interpolação de strings é extremamente útil e desejada do ponto de vista do desenvolvedor (e é por isso que está em tantas outras linguagens), mas parece que essa proposta está mal preparada para uma mudança tão grande neste momento, que se rejeitada, não acho significa que não vale a pena revisitar no futuro. As pequenas mudanças incrementais que tornariam isso óbvio ainda não estão em vigor.

Resolver dinamicamente variáveis ​​pelo nome do escopo não é algo que eu acho possível (embora yaegi Eval pareça fazer isso?) atualmente.

Acho que o post que @runeimp está referenciando é meu post em r/golang https://www.reddit.com/r/golang/comments/d1199a/why_is_there_no_equivalent_to_f_strings_in_python/
Onde há um pouco mais de discussão.

Eu só queria jogar meu chapéu no ringue e dizer que a interpolação variável seria muito útil para mim, vindo do mundo python, algo como fmt.sprintf faz Go parecer seriamente desajeitado e horrível de ler/manter.

Go é principalmente muito elegante na forma como faz as coisas que valoriza muito fácil e poderosa. Legibilidade e manutenção são alguns dos principais inquilinos de sua filosofia e realmente não deve ser tão difícil ler uma string e saber o que está acontecendo nela. Há uma quantidade seriamente não trivial de sobrecarga que vem com a compreensão do que será impresso, mantendo em mente quais variáveis ​​são mapeadas para quais itens em uma lista no final dessa string. Nós simplesmente não deveríamos precisar manter essa sobrecarga mental.

Se as pessoas acharem que fmt.sprintf é apropriado para elas, ele pode ser usado. Não acho que ter ambos prejudique o Go como linguagem, pois a proposta é inquestionavelmente mais legível que o método atual, intuitiva e fácil de manter. Isso não precisa ser justificado por interpolações de mais de 50 variáveis, é uma melhoria com interpolações de apenas uma variável.

É algo como

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

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

realmente uma perspectiva prejudicial para o idioma? Estou feliz por estar errado, mas honestamente só vejo vantagens nesta (ou algo assim) proposta, exceto pela incompatibilidade com versões anteriores, que é onde algo como implementar isso em uma nova versão principal do Go faz sentido

Poderia ser considerado deixar essa discussão aberta por mais um conjunto de 4 semanas, já que uma parte considerável da comunidade estava de férias para o Dia de Ação de Graças, Natal e Ano Novo quando o corte foi proposto?

Houve muito mais discussão desde https://github.com/golang/go/issues/34174#issuecomment -558844640, então estou retirando isso do final-comment-period.

Estou inclinado a concordar com @randall77 que ainda não vi um exemplo convincente aqui. @runeimp , obrigado por postar o código de exemplo, mas parece difícil de ler e difícil de alterar de qualquer maneira. Como sugere @egonelbre , se quisermos tornar isso mais sustentável, o primeiro passo parece ser encontrar uma abordagem totalmente diferente.

@cyclingwithelephants fmt.sprintf("I am %T{age} years old") não está na mesa aqui. A linguagem não fornece nenhum mecanismo que fmt.Sprintf possa usar para resolver age . Go é uma linguagem compilada e os nomes das variáveis ​​locais não estão disponíveis em tempo de execução. Isso seria mais palatável se pudéssemos descobrir alguma maneira de fazer isso funcionar, talvez nos moldes da sugestão de @bradfitz acima.

Obrigado @ianlancetaylor por levantar o rótulo do período de comentário final. 😀

Achei ótima a ideia do @bradfitz . Eu acho que existem limitações potenciais, não importa o que aconteça, sem um contexto de variável local, mas eu aceitaria alegremente essas limitações por não ter interpolação de strings. Embora eu tenha muito respeito pelas atualizações na formatação de porcentagem em Go (adoro as adições de %q , %v e %#v ), esse paradigma é antigo. Só porque é venerável não significa que seja a melhor maneira de fazer as coisas. Assim como a maneira de Go de lidar com a compilação, e especialmente a compilação cruzada é _WAY_ melhor do que como é feito com C ou C++. Agora, o Go apenas esconde todas as opções desagradáveis ​​do compilador com padrões sãos e é tão feio sob o capô. Não sei especificamente, mas acredito que seja o caso. E isso é bom. Isso é completamente aceitável para mim. Eu não me importo com o ritual obscuro que o compilador faz para fazer o recurso funcionar. Só sei que o recurso facilita a vida. E facilitou minha vida como desenvolvedor em todas as linguagens que usei que o suportam. E é sempre uma dor em idiomas que não o suportam. É significativamente mais fácil de lembrar do que como usar os mais de 30 caracteres especiais na formatação percentual para 98% da formatação de strings que preciso. E dizer para usar %v em vez de "o formato de porcentagem correto" não é a mesma facilidade de uso para criação nem perto da mesma facilidade de manutenção.

Agora que há um pouco mais de tempo, vou trabalhar em um exemplo e ver se consigo encontrar alguns artigos que sejam um pouco mais esclarecedores do que eu para ajudar a ilustrar os benefícios significativos da eficiência no trabalho para aqueles que lidam com interface humana , geração de documentos e códigos e manipulação de strings regularmente.

Aqui está um pensamento que pode levar a algo implementável. Embora eu não saiba que é uma boa ideia.

Adicione um novo tipo de string m"str" (e talvez o mesmo com uma string bruta). Esse novo tipo de literal de string é avaliado map[string]interface{} . Procurar a string vazia no mapa fornece o próprio literal da string. O literal de string pode conter expressões entre chaves. Uma expressão entre chaves é avaliada como se não estivesse no literal da string, e o valor é armazenado no mapa com a chave sendo a substring que aparece dentro das chaves.

Por exemplo:

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

Isso irá imprimir

twice i is {i * 2}
2

Dentro do literal de string, chaves podem ser escapadas com uma barra invertida para indicar uma chave simples. Uma chave sem aspas e sem correspondência é um erro de compilação. Também é um erro de compilação se a expressão dentro das chaves não puder ser compilada. A expressão deve ser avaliada para exatamente um valor, mas de outra forma não tem restrições. A mesma string entre chaves pode aparecer várias vezes no literal da string; ele será avaliado quantas vezes aparecer, mas apenas uma das avaliações será armazenada no mapa (pois todas terão a mesma chave). Exatamente qual é armazenado não é especificado (isso importa se a expressão for uma chamada de função).

Por si só, esse mecanismo é peculiar, mas inútil. Sua vantagem é que pode ser claramente especificado e, sem dúvida, não requer adições excessivas ao idioma.

O uso vem com funções adicionais. A nova função fmt.Printfm funcionará exatamente como fmt.Printf , mas o primeiro argumento não será um string mas sim um map[string]interface{} . O "" no mapa será uma string de formato. A string de formato suportará, além das coisas usuais % , um novo modificador {str} . O uso deste modificador significa que ao invés de usar um argumento para o valor, str será procurado no mapa, e esse valor será usado.

Por exemplo:

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

imprimirá a string hi passada para 20 espaços.

Naturalmente haverá o fmt.Printm mais simples que substituirá para cada expressão entre colchetes o valor contido conforme impresso por fmt.Print .

Por exemplo:

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

vai imprimir

i: 1; j: 2

Problemas com esta abordagem: o uso estranho de um prefixo m antes de uma string literal; o m duplicado em uso normal - um antes e um depois dos parênteses; a inutilidade geral de uma m-string quando não usada com uma função que espera uma.

Vantagens: não é muito difícil de especificar; suporta interpolação simples e formatada; não limitado a funções fmt, portanto, pode funcionar com modelos ou usos imprevistos.

Se fosse um mapa digitado (por exemplo, runtime.StringMap), poderíamos usar fmt.Print e Println sem adicionar o Printfm stuttery

Usar um tipo definido é uma boa ideia, mas não ajudaria com fmt.Printfm ; não poderíamos usar o tipo definido como o primeiro argumento para fmt.Printf , pois isso leva apenas um string .

Uma coisa que foi mencionada anteriormente no tópico por @runeimp , mas não foi totalmente discutida é 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.
}

Embora isso seja muito detalhado para casos simples, é muito mais palatável quando você tem um número grande de valores a serem interpolados (embora qualquer abordagem tenha um problema com 70 valores!). As vantagens incluem: -

  1. Se você usar uma closure para a função de mapeamento, ela lidará bem com variáveis ​​locais.

  2. Ele também lida bem com formatação arbitrária e mantém isso fora da própria string interpolada.

  3. Se você usar um espaço reservado na string interpolada que não está presente na função de mapeamento, ele será substituído automaticamente por uma string vazia.

  4. As alterações na função de mapeamento são relativamente fáceis de fazer.

  5. Já o temos - não são necessárias alterações de idioma ou biblioteca.

@ianlancetaylor essa solução parece uma opção sólida para mim. Embora eu não esteja vendo por que precisamos de métodos alternativos de impressão. Provavelmente estou ignorando algo, mas parece uma simples alteração de assinatura usando interface{} e verificação de tipo. OK, acabei de perceber como a mudança de assinatura pode ser muito problemática para alguns códigos existentes. Mas se o mecanismo básico foi implementado e também criamos um tipo stringlit que representa string ou m"str" ou se m"str" também aceita string isso seria uma mudança aceitável para o Go v2? Ambos são "literais de string", só que um deles tem essencialmente um sinalizador que permite funcionalidades extras, não?

Obrigado por trazer isso de novo @alanfo , esses são todos excelentes pontos. 😃

Eu usei os.Expand para modelagem de luz e pode ser muito útil em situações em que você precisa construir um mapa de valores de qualquer maneira. Mas se o mapa não for necessário e você precisar fazer seu fechamento em várias áreas diferentes apenas para capturar as variáveis ​​locais para sua função de substituição (agora copiada muitas vezes), a função de substituição ignora completamente DRY e pode levar a problemas de manutenção e apenas adiciona mais trabalho se as strings interpoladas "apenas funcionariam", aliviariam esses problemas de manutenção e não exigiriam a construção de um mapa apenas para gerenciar essa string dinâmica.

@runeimp Não podemos alterar a assinatura de fmt.Printf . Isso quebraria a compatibilidade do Go 1.

A noção de um tipo stringlit implica mudar o sistema de tipos de Go, que é um negócio muito maior. Go intencionalmente tem um sistema de tipos muito simples. Eu não acho que queremos complicar para este recurso. E mesmo que o fizéssemos, fmt.Printf ainda receberia um argumento string , e não podemos mudar isso sem quebrar os programas existentes.

@ianlancetaylor Obrigado pelo esclarecimento. Eu aprecio o desejo de não quebrar a compatibilidade com algo tão fundamental quanto o pacote fmt ou o sistema de tipos. Eu estava apenas esperando que pudesse haver alguma possibilidade oculta (para mim) que pudesse ser uma opção nesse sentido de alguma forma. 👼

Eu realmente gosto da maneira Ian de implementar isso. Os genéricos não ajudariam com a questão 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 { 
  // ...
}

Desta forma, a compatibilidade com Go 1 deve ser preservada.

Para compatibilidade com Go 1, não podemos alterar o tipo de uma função. As funções não são apenas chamadas. Eles também são usados ​​em código como

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

As pessoas escrevem código assim ao fazer tabelas de funções ou ao usar injeção de dependência rolada manualmente para testes.

Tenho a sensação de que strings.Replacer (https://golang.org/pkg/strings/#Replacer) quase pode fazer interpolação de strings, apenas faltando o identificador de interpolação (por exemplo, ${...}) e o processamento de padrão (por exemplo, se var i int = 2 , "${i+1}" deve ser mapeado para "3" no substituto)

Ainda outra abordagem teria uma função embutida, digamos, format("Eu sou um %(foo)s %(bar)d") que se expande para fmt.Sprintf("Eu sou um %s %d", foo, Barra). Pelo menos, isso é totalmente compatível com versões anteriores, FWIW.

De uma perspectiva de design de linguagem, seria peculiar ter uma função interna expandida para uma referência a uma função na biblioteca padrão. Para fornecer uma definição clara para todas as implementações da linguagem, a especificação da linguagem teria que definir completamente o comportamento de fmt.Sprintf . O que acho que queremos evitar.

Isso provavelmente não deixará todos felizes, mas acho que o abaixo seria o mais geral. Está dividido em três partes

  1. Funções fmt.Printm que recebem uma string de formato e um map[string]interface{}
  2. aceite #12854 para que você possa soltar o map[string]interface{} ao chamá-lo
  3. permitir nomes sem chave em literais de mapa como abreviação de "name": name, ou "qual.name": qual.name,

Juntos, isso permitiria 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,
})

Isso ainda tem a duplicação entre a string de formato e os argumentos, mas é muito mais leve na página e é um padrão facilmente automatizado: um editor ou ferramenta pode preencher automaticamente o {i, j} com base na string e no compilador deixaria você saber se eles não estão no escopo.

Isso não permite que você faça cálculos dentro da string de formato, o que pode ser bom, mas já vi isso exagerado o suficiente para considerá-lo um bônus.

Como se aplica a literais de mapa em geral, pode ser usado em outros casos. Costumo nomear minhas variáveis ​​de acordo com a chave que elas estarão no mapa que estou construindo.

Uma desvantagem disso é que ele não pode ser aplicado a structs, pois eles podem ser descodificados. Isso poderia ser corrigido exigindo um : antes do nome como {:i, :j} e então você poderia fazer

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

Precisamos de algum suporte de idioma para isso? Como está agora, pode ficar assim, com um tipo de mapa ou com uma API fluida e mais segura para o tipo:

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, ainda é ineficiente, mas parece que não dá muito trabalho fazer um pacote que suporte isso. Como bônus, incluí uma API fluida diferente que também simula interpolações.

A proposta "map string" de @ianlancetaylor (embora eu pessoalmente prefira "value/variable string" com sintaxe av"...") também permite casos de uso sem formatação. Por exemplo, #27605 (funções de sobrecarga de operador) existe em grande parte porque é difícil hoje fazer uma API legível para math/big e outras bibliotecas numéricas. Esta proposta permitiria a função

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

Usado como

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

É importante ressaltar que essa função auxiliar pode coexistir com a API mais eficiente e poderosa que existe atualmente.

Essa abordagem permite que a biblioteca execute as otimizações que desejar para expressões grandes e também pode ser um padrão útil para outras DSLs, pois permite a análise de expressões personalizadas enquanto ainda representa valores como variáveis ​​Go. Notavelmente, esses casos de uso não são suportados pelas f-strings do Python porque a interpretação dos valores incluídos é imposta pela própria linguagem.

@HALtheWise Obrigado, isso é muito legal.

Queria comentar para mostrar um pouco de apoio a essa proposta, da postura de um desenvolvedor geral. Eu tenho codificação com golang por mais de 3 anos profissionalmente. Quando mudei para golang (de obj-c/swift), fiquei desapontado porque a interpolação de strings não foi incluída. Eu usei C e C++ por mais de uma década no passado, então printf não quer um ajuste específico, além de sentir vontade de voltar um pouco - descobri que realmente faz diferença na manutenção e legibilidade do código para strings mais complexas. Recentemente, fiz um pouco de kotlin (para um sistema de compilação gradle) e usar a interpolação de strings foi uma lufada de ar fresco.

Acho que a interpolação de strings pode tornar a composição de strings mais acessível para aqueles que são novos na linguagem. Também é uma vitória para UX técnico e manutenção, devido à redução da carga cognitiva tanto na leitura quanto na escrita do código.

Congratulo-me com o facto de esta proposta estar a ser realmente considerada. Aguardo a resolução da proposta. =)

Se bem entendi, a proposta da @ianlancetaylor é:

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

Depois disso, uma função de impressão lidará com esse mapa, analisará o modelo inteiro novamente e tirará algumas vantagens do modelo pré-analisado

Mas se expandirmos m"str" ​​para uma função?

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 função tem a seguinte assinatura:

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

Essa função terá um desempenho melhor porque, para aproveitar mais o modelo pré-analisado, muito mais otimizações podem ser feitas de maneira semelhante ao Rust com a função println! .

O que estou tentando descrever aqui é muito parecido com as funções Tagged do Javascript, e poderíamos discutir se o compilador deve aceitar funções do usuário para formatar string ex:

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

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

@rodcorsi Se estou lendo sua sugestão corretamente, é necessário construir a formatação fmt.Printf na linguagem adequada, porque o compilador terá que entender onde %20s começa e termina. Essa é uma das coisas que eu estava tentando evitar.

Observe também que minha sugestão não está vinculada à formatação fmt.Printf e também pode ser usada para outros tipos de interpolação.

Eu me oporia a tratar m"..." como expansão para uma chamada de função, porque obscurece o que realmente está acontecendo e adiciona o que é efetivamente uma segunda sintaxe para chamadas de função. Geralmente parece razoável passar uma representação mais estruturada do que um mapa, para evitar a necessidade de reimplementações correspondentes do comportamento de análise em todos os lugares. Talvez uma estrutura simples com uma fatia de seções de string constantes, uma fatia de strings para coisas entre chaves e uma fatia de interface?

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

A segunda e a terceira fatias devem ter o mesmo comprimento e a primeira deve ser uma mais longa. Existem outras maneiras de representar isso também para codificar essa restrição estruturalmente.
A vantagem que isso tem sobre um formato que expõe diretamente a string original é que há um requisito mais flexível de ter um analisador correto e de alto desempenho na função que a consome. Se não houver suporte para caracteres de escape ou m-strings aninhadas, provavelmente não é grande coisa, mas prefiro não precisar reimplementar e testar esse analisador, e armazenar em cache o resultado pode causar vazamentos de memória de tempo de execução.

Se "opções de formatação" é um desejo frequente de coisas usando essa sintaxe, eu poderia ver que há um lugar para elas na especificação, mas eu pessoalmente usaria uma sintaxe como m"{name} is {age:%.2f} years old" onde o compilador apenas passa tudo após o : para a função.

Olá, eu queria comentar sobre isso para adicionar suporte a esta proposta. Eu tenho trabalhado com muitas linguagens diferentes nos últimos 5 anos (Kotlin, Scala, Java, Javascript, Python, Bash, algumas C, etc) e estou aprendendo Go agora.

Acho que a interpolação de strings é obrigatória em qualquer linguagem de programação moderna, da mesma forma que a inferência de tipos, e temos isso em Go.

Para aqueles argumentando que você pode realizar a mesma coisa com o Sprintf, então, não entendo por que temos inferência de tipos em Go, você poderia realizar a mesma coisa escrevendo o tipo certo? Bem, sim, mas o ponto aqui é que a interpolação de strings reduz muito a verbosidade que você precisa para conseguir isso e é mais fácil de ler (com Sprintf você tem que pular para a lista de argumentos e a string para trás e para fazer sentido a corda).

No software da vida real, este é um recurso muito apreciado.

É contra o design minimalista Go? Não, não é um recurso que permite fazer coisas malucas ou abstrações que complicam seu código (como herança), é apenas uma maneira de escrever menos e adicionar clareza ao ler o código, o que acredito não ser contra o que Go é tentando fazer (temos inferência de tipos, temos o operador :=, etc).

Formatação correta para um idioma com digitação estática

Haskell possui bibliotecas e extensões de linguagem para interpolação de strings. Não é uma coisa de tipo.

Esta página foi útil?
0 / 5 - 0 avaliações