Go: 提案:文字列補間

作成日 2019ĺš´09月08日  Âˇ  67コメント  Âˇ  ソース: golang/go

序章

それが何であるかを知らない人のために:

迅速

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
文字列名="マーク";
var date = DateTime.Now;

Console.WriteLine($ "こんにちは、{名前}!今日は{date.DayOfWeek}、今は{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

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


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


var status fmt.Stringer
…
msg:= "終了ステータス:{ステータス}" // msg:= "終了ステータス:" + status.String()


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

# Syntax proposed

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

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

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

「」{変数}「」

Formatting also has formatting options. It may look like

「」{変数[:]}「」

#### Examples of options

v:= 123.45
fmt.Println( "value = {v:04.3}")// value = 0123.450


v:="値"
fmt.Println( "value ='{v:a50}'")// value='<45スペース>値'

# Conversions

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

タイプフォーマッタインターフェイス{
Format(フォーマット文字列)文字列
}
`` `

補間オプションを処理するために後で導入できます

従来のフォーマットに対する長所と短所

長所

  • 型安全性
  • パフォーマンス(コンパイラによって異なります)
  • カスタムフォーマットオプションは、ユーザー定義タイプをサポートします

短所

  • コンパイラの複雑さ
  • サポートされている書式設定方法が少ない( %v (?)、 %Tなどはありません)
Go2 LanguageChange Proposal

最も参考になるコメント

私にとって、言語に文字列補間を含める理由としてのタイプチェックはそれほど魅力的ではありません。 fmt.Printfに変数を書き込む順序に依存しない、より重要な理由があります。

提案の説明から例の1つを取り上げて、文字列補間を使用する場合と使用しない場合のGoで記述します。

  • 文字列補間なし(現在のGo)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
  • 文字列補間と同等の機能(およびフォーマットオプションを表現するために必要な@bradfitzコメントの混合)
name := "Mark"
date := time.Now()

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

私にとって、2番目のバージョンは、変数を書き込む順序に依存しないため、読み取りと変更が簡単で、エラーが発生しにくくなっています。

最初のバージョンを読むときは、行を数回読み直して正しいことを確認する必要があります。目を前後に動かして、「%v」の位置と配置する必要のある場所の間にメンタルマッピングを作成します。変数。

私は、データベースクエリが多くの「?」で記述されていたいくつかのアプリケーション(Goで記述されていませんが、同じ問題で)のバグを修正してきました。 名前付きパラメーター( SELECT * FROM ? WHERE ? = ? AND ? != false ... )の代わりに、さらに変更を加えると(gitマージ中に不注意でさえ)、2つの変数の順序が反転しました😩

したがって、長期的な保守性を容易にすることを目的とする言語の場合、この理由から、文字列補間を検討する価値があると思います。

これの複雑さについて:Goコンパイラの内部がわからないので、一言で言えば、_上記の例で示した2番目のバージョンを最初のバージョンに変換するだけではないでしょうか?_

全てのコメント67件

$ {...}だけを使用できないのはなぜですか? Swiftにはすでに1つの構文、JavaScriptとKotlin、C#、さらにPerlがあります...なぜもう1つのバリエーションを発明するのですか? 最も読みやすく、すでに存在するものに固執する方が良いのではないでしょうか?

$ {...}だけを使用できないのはなぜですか? Swiftにはすでに1つの構文、JavaScriptとKotlin、C#、さらにPerlがあります...なぜもう1つのバリエーションを発明するのですか? 最も読みやすく、すでに存在するものに固執する方が良いのではないでしょうか?

${…}の既存の文字列がある可能性があるためです。 たとえば、私はそうします。

また、現在\{は許可されていません。

これは、 fmt.Sprintfを呼び出すよりも大きな利点はないようです。

これは、 fmt.Sprintfを呼び出すよりも大きな利点はないようです。

はい、それは完全なタイプの安全性、より良いパフォーマンスと使いやすさ以外にはありません。 静的型付けを使用する言語に対して正しくフォーマットされます。

私が知る限り、この提案はfmt.Sprintfを%vで使用するよりもタイプセーフではありません。 型安全性に関しては本質的に同じです。 多くの場合、注意深く実装するとパフォーマンスが向上する可能性があることに同意します。 私は使いやすさにとらわれません。

これを実装するには、言語仕様で、すべてのタイプに使用する正確なフォーマットを記述する必要があります。 スライス、配列、マップをフォーマットする方法を決定し、文書化する必要があります。 インターフェース。 チャネル。 これは、言語仕様への重要な追加になります。

それは、両方の方法が存在する権利を持っているそれらの質問の1つであり、決定するのは意思決定者次第だと思います。 Goでは、かなり前に決定がすでに行われており、それは機能し、慣用的です。 これfmt.Sprintfと%vです。

歴史的に、文字列内の補間が最初から存在していた言語があります。 特にそれはPerlです。 これが、Perlが非常に人気になった理由の1つです。これは、C et alのsprintf()と比較して非常に便利だったためです。 そして、 %vはまだ発明されていませんでした。 そして、補間が存在する言語がありますが、構文的には不便でした。 "text" + v1 + "text"を考えてみてください。 次に、JavaScriptは、複数行であり、 ${...}内の任意の式の補間をサポートするバックティック引用リテラルを導入しました。これは、 "text" + v1 + "text"と比較して大幅に改善されました。 Goにもバックティックの複数行リテラルがありますが、 ${...}はありません。 誰が私が知らない人をコピーしたのか。

これをサポートするにはかなりの努力が必要であるという@ianlancetaylorに同意しません。 実際、 fmt.Sprintfと%vはまさにこれを実行しますね? 私は、内部でまったく同じものとは異なる構文ラッパーのように見えます。 私は正しいですか?

しかし、__私は@ianlancetaylorに同意します__。 fmt.Sprintfを%vで使用するのも同様に便利です。 また、画面上の長さもまったく同じであり、IMHOが非常に重要なのは、Goではすでに慣用句です。 それは一種のGoGoになります。 他のすべての言語から他のすべての機能をコピー実装すると、Goではなく他のすべての言語になります。

もう1つあります。 文字列補間が最初から存在していたPerlでの長年の経験から、それはそれほど完璧ではないと言えます。 それには問題があります。 単純で些細なプログラムの場合、そしておそらくすべてのプログラムの90%の場合、変数の文字列補間は問題なく機能します。 しかし、ときどきvar=1.99999999999を取得し、それを1.99として出力したい場合、標準の文字列補間では__できません__。 事前に変換を行う必要があります。または...ドキュメントを調べて、長い間忘れられていたsprintf()構文を再学習してください。 ここで__is__問題-文字列補間を使用すると、sprintf()のような構文の使用方法を忘れることができ、おそらくそれは非常に存在します。 そして、それが必要なとき-あなたは最も単純なことをするのにあまりにも多くの時間と労力を費やします。 私はPerlの文脈で話しているのですが、そうするのは言語設計者の決定でした。

しかし、囲碁では、別の決定がなされました。 %v fmt.Sprintf #$はすでにここにあり、補間された文字列と同じくらい便利で、短い__です。また、ドキュメント、例、あらゆる場所にあるという点で__idiomatic__です。 そして、1.9999999999を1.99として印刷する方法を最終的に忘れるという問題に悩まされることはありません。

提案された構文を導入すると、GoはSwiftやJavaScriptに少し似たものになり、一部の人はそれを好むかもしれません。 しかし、この特定の構文は、少し悪くはないにしても、それを良くすることはないと思います。

物事を印刷する既存の方法はそのままにしておくべきだと思います。 そして、誰かがもっと必要な場合は、そのためのテンプレートがあります。

ここでの議論の一部がコンパイル時の安全性である場合、それが説得力のある議論であることに同意しません。 go vet 、および拡張機能go testは、しばらくの間、 fmt.Sprintfの誤った使用にフラグを立ててきました。

本当に必要な場合は、 go generateを使用して今日のパフォーマンスを最適化することもできます。 これは、ほとんどの場合価値がないトレードオフです。 スペックを大幅に拡大する場合も同様だと思います。 トレードオフは一般的に価値がありません。

補間された文字列内で関数呼び出しを許可するのは残念なことです-見逃しがちです。
それらを許可しないことは、もう1つの不要な特殊なケースです。

ここでの議論の一部がコンパイル時の安全性である場合、それが説得力のある議論であることに同意しません。 go vet、ひいてはgo testは、しばらくの間、fmt.Sprintfの誤った使用にフラグを立ててきました。 本当に必要な場合は、gogenerateを使用して今日のパフォーマンスを最適化することもできます。 これは、ほとんどの場合価値がないトレードオフです。 スペックを大幅に拡大する場合も同様だと思います。 トレードオフは一般的に価値がありません。

ただし、型の安全性は、ツールではなくコンパイラによって保証される必要があります。 これはセマンティクスです。 これは、オプションや明示的にnull許容値を宣言する代わりに、nullチェックを忘れた場所を確認するためのツールが必要だと言っているようなものです。

それとは別に、これを安全にする唯一の方法は、依存型を使用することです。 文字列補間は、 fmt.Sprintfと同じものの糖衣構文であり、私はすべて良い砂糖に賛成ですが、goコミュニティ全体はそうではないようです。

あるいは、fmt.Printfとその仲間たちとうまく機能するように言語を変更するようなものかもしれません。

fmtが%(foo)vや%(bar)qのようなものをサポートしている場合のように、可変個引数関数/メソッドの呼び出しで%(<ident>)を含む文字列リテラルが使用されている場合、参照されているすべてのシンボルは可変個引数リストに自動的に追加されます。

例:このコード:

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

本当にコンパイルします:

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

そして、 fmtは、不要な(name) #$ビットと(age)ビットをスキップすることができます。

ただし、これは非常に特殊なケースの言語変更です。

私にとって、言語に文字列補間を含める理由としてのタイプチェックはそれほど魅力的ではありません。 fmt.Printfに変数を書き込む順序に依存しない、より重要な理由があります。

提案の説明から例の1つを取り上げて、文字列補間を使用する場合と使用しない場合のGoで記述します。

  • 文字列補間なし(現在のGo)
name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())
  • 文字列補間と同等の機能(およびフォーマットオプションを表現するために必要な@bradfitzコメントの混合)
name := "Mark"
date := time.Now()

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

私にとって、2番目のバージョンは、変数を書き込む順序に依存しないため、読み取りと変更が簡単で、エラーが発生しにくくなっています。

最初のバージョンを読むときは、行を数回読み直して正しいことを確認する必要があります。目を前後に動かして、「%v」の位置と配置する必要のある場所の間にメンタルマッピングを作成します。変数。

私は、データベースクエリが多くの「?」で記述されていたいくつかのアプリケーション(Goで記述されていませんが、同じ問題で)のバグを修正してきました。 名前付きパラメーター( SELECT * FROM ? WHERE ? = ? AND ? != false ... )の代わりに、さらに変更を加えると(gitマージ中に不注意でさえ)、2つの変数の順序が反転しました😩

したがって、長期的な保守性を容易にすることを目的とする言語の場合、この理由から、文字列補間を検討する価値があると思います。

これの複雑さについて:Goコンパイラの内部がわからないので、一言で言えば、_上記の例で示した2番目のバージョンを最初のバージョンに変換するだけではないでしょうか?_

@ianlancetaylorは、上記のスケッチ(https://github.com/golang/go/issues/34174#issuecomment-532416737)は、動作が変更されるまれなプログラムがある可能性があるため、厳密には下位互換性がないことを指摘しました。

下位互換性のあるバリエーションは、たとえばfというプレフィックスが付いた新しいタイプの「フォーマットされた文字列リテラル」を追加することです。

例:このコード:

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

本当にコンパイルします:

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

ただし、2倍のf ( Printfに1つ、新しいタイプの文字列リテラルの前にfが続く)は途切れます。

また、コンパイラの内部動作も理解していないので、(おそらくばかげて)これもコンパイラに実装できると思います。
fmt.printf("Hello %s{name}. You are %d{age}")

同等の現在の定式化にコンパイルされます。

文字列補間には、読みやすさが向上するという明らかな利点があり(Goのコア設計の決定)、処理する文字列が長く複雑になるにつれてスケーリングも向上します(Goの別のコア設計の決定)。 {age}を使用すると、文字列をスキム読み取りするだけの場合(そしてもちろん指定されたタイプを無視する場合)にはない文字列コンテキストが得られることにも注意してください。文字列は「あなたは背が高い」で終わっている可能性があります。 「あなたは[XXXの場所にいる]」、「あなたは一生懸命働いています」、そしてフォーマットメソッドを補間の各インスタンスにマッピングするために精神的なエネルギーを投入しない限り、そこに何をすべきかはすぐにはわかりません。 この(確かに小さい)精神的なハードルを取り除くことにより、プログラマーはコードではなくロジックに集中することができます。

コンパイラは言語仕様を実装します。 言語仕様は現在、fmtパッケージについて何も述べていません。 する必要はありません。 fmtパッケージをまったく使用しない大規模なGoプログラムを作成できます。 fmtパッケージのドキュメントを言語仕様に追加すると、それが著しく大きくなります。これは、言語がはるかに複雑になるという別の言い方です。

それでこの提案を採用することは不可能ではありませんが、それは大きなコストであり、そのコストを上回るには大きな利益が必要です。

または、fmtパッケージを使用せずに文字列補間について説明する方法が必要です。 これは、文字列型の値、または[]byte型の値についてはかなり明確ですが、他の型の値についてはそれほど明確ではありません。

@IanLanceTaylorが上記で述べたことと、フォーマットオプションを使用して複雑な式を補間しようとすると、読みやすさの利点がウィンドウの外に出る傾向があるため、この提案には賛成できません。

また、関数のfmt.Print (およびPrintln )ファミリーに可変個引数を含める機能により、ある形式の補間がすでに有効になっていることを忘れることがあります。 前に引用した例のいくつかは、私の考えでは同じように読みやすい次のコードで簡単に再現できます。

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

それでも言語で追加して__have__するもう1つの理由: https ://github.com/golang/go/issues/34403#issuecomment -542560071

https://github.com/golang/go/issues/34174#issuecomment-540995458にある@alanfoのコメントは説得力がありますfmt.Sprintを使用して単純な種類の文字列補間を行うことができます。 構文はおそらくあまり便利ではありませんが、この方法では、どのような場合でも変数を補間するための特別なマーカーが必要になります。 また、前述のように、これにより、値の任意のフォーマットを補間できます。

上記のように、これをほぼ行うための既存の方法があり、個々の変数のフォーマットも可能です。 したがって、これはおそらく減少です。 最終的なコメントのために4週間開いたままにします。

私は定期的に、挿入する変数が50をはるかに超えるテキストブロックの作成に直面しています。 いくつかのケースでは70以上。 これは、Pythonのf文字列(上記のC#と同様)を使用して簡単に維持できます。 しかし、いくつかの理由から、PythonではなくGoでこれを処理しています。 これらのブロックを管理するためのfmt.Sprintfの初期設定は...わかりました。 しかし、神は私が間違いを修正したり、 %anythingマーカーとその位置に関連する変数を移動または削除することを含む何らかの方法でテキストを変更しなければならないことを禁じています。 また、 templateに渡すマップを手動で作成したり、 os.Expandを設定したりすることも、優れたオプションではありません。 曜日を問わず、(セットアップの)速度とf-stringの保守性の容易さをfmt.Sprintf以上にします。 いいえ、 fmt.Sprintはそれほど有益ではありません。 この場合、 fmt.Sprintfよりもセットアップが簡単です。 しかし、弦に飛び込んだり飛び出したりするため、視覚的にはその意味の多くが失われます。 "My {age} is not {quality} in this discussion"は、 "My ", age, " is not ", quality, " in this discussion"のように文字列に飛び込んだり飛び出したりすることはありません。 特に何十もの参考文献の過程で。 テキストと参照を移動するには、f文字列をコピーして貼り付けるだけです。 削除は選択して削除するだけです。 あなたはいつも文字列の中にいるからです。 これは、 fmt.Sprintを使用する場合には当てはまりません。 誤って(または必然的に)文字列以外のコンマまたは二重引用符で囲まれた文字列の終了を選択し、フォーマットを解除して_元の場所にマッサージする_ために編集を要求することは非常に簡単です。 これらの場合のfmt.Sprintとfmt.Sprintfは、f文字列に似たものよりもはるかに時間がかかり、エラーが発生しやすくなります。

それはかなり恐ろしい仕事のように聞こえますが、あなたはそれをします!

そのような問題に直面した場合、最初は確かにtext/templateの観点から考えているでしょう。あるいは、変数を構造体やマップに入れるのが面倒な場合は、おそらくfmt.Sprintを好むでしょう。 fmt.Sprintfですが、コードは次のように配置します。

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

それは多くの画面領域を占有しますが、間違いを犯すリスクがあまりないので、物事を変更、削除、または移動するのは比較的簡単です。

いくつかのgo文字列補間ライブラリがありますが、共用体型やジェネリックスなどの言語機能がないため、他の言語ほど柔軟でも流動的でもありません。

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

または、 go.uber.org / yarpc / internal / interpolate

繰り返しになりますが、 template/textを使用できるようにマップを作成すると、サードパーティのライブラリまたはos.Expandの最小限のシステムが、変数用に作成する必要のあるキーが50個以上ない場合に最適です。問題のコードの残りの部分にはすでに存在しています。 そして、この機能に対する唯一の本当の議論は、「%フォーマットを適切に使用する方法を忘れる可能性がある」であり、効率や特異性などが絶対に必要な1つのインスタンスのフォーマットを検索するためにGoogleを使用してさらに2分を費やす必要があるかもしれませんfmt.Sprintf ? 一方、ほとんどの場合、効率、特異性、またはfmt.Sprintfのいずれかが必要であり、常に使用するため、フォーマットをまったく忘れないでください。 問題はわかりません。

他の議論はそれが言語をより複雑にするということですか? はい、そうです。 技術的には、言語に追加すると複雑さが増します。 そして、私はすべて、些細な理由で言語をより複雑にしないことに賛成です。 私は_REALLY_で、Pythonで使用されるすべての方法でinを使用することを望んでいます。 しかし、私は:= rangeをinに置き換えるだけで解決します。 私はこの1年間、Pythonにほとんど触れておらず、2年前にしか使用していませんでした。 しかし、10回のうち9回は、最初にinと入力してから、 := rangeで修正します。 しかし、私はそれをめぐって戦うつもりはありません。 := rangeは、以前に作成したコードを推論するのに十分で明白です。 しかし、fストリングとその種類はまったく異なります。 それがハードコアの機能、使いやすさ、そして保守性です。 それは絶対にGo仕様の一部である必要があります。 サードパーティのライブラリとしてそれを行うための施設があれば、私はライブラリを作成してそれで終わります。 しかし、私の知る限り、Go言語への追加の拡張なしでは実現可能ではありません。 したがって、少なくとも、言語への拡張が行われるべきだと思います。

@runeimp "My {age} is not {quality} in this discussion"のサポートを開始できるというあなたの提案が機能しないことを明確にしておきたいと思います。 これは今日有効なGo文字列であり、 ageとqualityを補間すると予想される場合、既存のGoプログラムが破損します。 "My \{age} is not \{quality} in this discussion"のようなものでなければなりません。 また、文字列以外の値のフォーマットの問題は残っています。

@ianlancetaylorに感謝します。 明確にするために、Pythonのf文字列はf"My {age} is not {quality} in this discussion"またはF'My {age} is not {quality} in this discussion' 、あるいはfと一重引用符または二重引用符の任意の組み合わせです。 前述のように、C#がそれを行うように見える方法と非常によく似ています。 また、Pythonのf-stringまたはC#スタイルを好みますが、同様の使用法を容易にする_ANYTHING_を絶対に受け入れます。 InterPolationACTIVATE"My @$@age###^&%$ is not @$@quality###^&%$ in this discussion"INTERpolationDEACTVATEandNullifyは許容されます。 ラメ、しかし許容できる。 そして、私は冗談を言っていません。 私は、曜日を問わず、 fmt.Sprintfを超える文字列補間の言い訳をします。 メンテナンスのメリットだけでもそれは正当化されます。

私はそれがあなたの特定のケースに役立つことを理解しています。

https://blog.golang.org/go2-here-we-come @griesemerは、言語を変更する場合は

  1. 多くの人々にとって重要な問題に取り組み、
  2. 他のすべての人への影響は最小限であり、
  3. 明確でよく理解されたソリューションが付属しています。

これは多くの人にとって重要な問題ですか?

わかりませんが、そうなると思います。 これは、短い文字列で5つ以下の参照を処理する必要があっただけの人にとっては重要ではない機能だと思います。 または、他の仕様要件のために、とにかくテンプレートを使用して常に管理する必要がありました。 しかし、それを使用している私たちにとっては。 存在しないときはかなり見逃されます。 また、オプションがなかった場合(Goの前にC、C ++などを使用したことがある場合のみ)、経験の例はすぐに忘れられるか、または彼らが問題点を忘れようとして対処しなければならなかった最悪のプロジェクトとして思い出しました。 ほとんどのGo開発者が、実際にローカル変数の文字列補間をサポートする言語を使用していない限り、この問題に関して多数派の回答が得られるとは思えません。

これは、20年以上前にプロのWeb開発者として始めて以来、私が常に遭遇している問題です。 これらのほとんどの場合、テンプレートはフロントエンドとバックエンドで一般的でした。 私のキャリアのその部分の最後で、私は最終的にRuby on Railsで作業し、Rubyの"My #{age} is not #{quality} in this discussion"文字列補間にすぐに夢中になりました。 ポンド-中-中括弧の構文は、最初は非常に奇妙だと思いましたが。 統合エンジニアリングに移行したとき、私は主にPython 3を使用しており、古い%形式の文字列の代わりに新しいstr.format()システムを使用する方がはるかに幸せでした。 それを使用すると、 "My {} is not {} in this discussion".format(age, quality)のようなことをします。 したがって、少なくともタイプに依存しない参照を使用することは、ユースケースの90%では重要ではありませんでした。 これは理解するのが簡単で、インデックスだけに関心があります。 "My {age} is not {quality} in this discussion".format(age=my_age_var, quality=my_quality_var)のような名前の付いた参照。 これで、同じ変数が30回参照される場合、パラメーターで1回指定するだけで、追跡、コピー&貼り付け、または削除が簡単になります。 名前付きパラメーター(入力として)は、Goで見逃しているPythonのもう1つの機能です。 しかし、必要に応じて私はそれなしで生きることができます。 しかし、私がそれに目を向けた瞬間、f-strings(Python 3.6で導入された)に再び恋をしました。 文字列補間は常に私の生活を楽にしてくれました。

@runeimpこれらの50以上の可変文字列補間の1つの例を投稿できますか(使用している任意の言語で)? 問題のコードの実際の例を持っていると、議論に役立つかもしれないと思います。

OK、これは最終的なコードではなく、単純な例であり、参照は50個しかなく、変数名は無実を保護するために変更されています。

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

潜在的な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}`,
    )
}

今後数年間、毎月維持および追加、更新、削除するのは2つのうちどちらですか。

どちらも選択できませんか? どちらも私には恐ろしいように見えます。

fmt.Sprintf("%#v", dt)でできないあなたのケースはどうですか? つまり、注文の要件は何ですか? 正確なフォーマット(例:セパレーターの=と: 、 %qと%v 、...)? 印刷されていないフィールド? 改行?

他のプログラムが出力を解析していますか、それともこれは人間が消費するためのものですか?

リフレクトを使用するのはどうですか?

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

この非常に具体的な例では、これらのオプションはどちらも妥当です。 しかし、それはもっと複雑なことにはなりません。 または文字通り他のもの。 同じ参照が1つの文字列で何度も使用される場合のように。 または、値が単一の構造体またはマップの一部ではなく、同じスコープで生成されます。 これは単なる例です。 残念ながら、実際のコードを表示することはできません。 しかし、実際には、何百もの単語が含まれているWebページを見てください。 ソースビューを開き、50以上の単語が動的である必要があることを想像してください。 これがWebページ用ではないことを想像してみてください。他の理由でテンプレートシステムは必要ありませんが、この問題を解決するために、すべての変数がすでにスコープ内にあり、コードの他の部分や複数の場所で使用されています。このコードブロックでは、このテキストは3〜4週間ごとに大きく、非常に重要な方法で変更される場合があります。 これは、PDF、CSV、SQL INSERT、電子メール、API呼び出しなどの一部またはすべてを_時々_生成する必要があるシステムの一部である可能性があります。

実際の例がなければ、この提案が問題の正しい解決策であるかどうかを判断するのは非常に困難です。 おそらく文字列補間が本当に解決策であるか、あるいはXY問題に直面しているのかもしれません。 おそらく、正しい解決策はすでにどこかの言語にあるか、解決策はパッケージリフレクトへの変更、またはおそらくパッケージテキスト/テン​​プレートです。

まず、これが多くの人にとって重要な問題であるかどうかは疑問です。 これは私がそれについて見て思い出すことができる唯一の真剣な議論であり、絵文字投票は圧倒的な支持を示唆していません。 サードパーティのライブラリも正確に豊富ではありません。

次に、fmt.Printf(およびファミリ)でさえ繰り返し引数を実行できることを指摘する価値があります。

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

第三に、他の言語で文字列補間を使用したことがある人がGoでそれを見たいと思うとは限らないと思います。 私は以前にいくつかの言語でそれを使用しましたが、単純な変数を補間したい場合は問題ありませんが、より複雑な式、関数呼び出し、エスケープされたフラグ文字などを使用して、それらすべてにフォーマットの詳細を追加しようとするとすぐに(これは必然的に「printf」スタイルのアプローチを意味します)全体がすぐに読めない混乱になる可能性があります。

最後に、補間する変数が多数ある場合は、 text/templateが最善のアプローチであると思われるため、変数がこの複雑さの何かを言語自体に統合するのではなく、明確に定義された構造の一部ではありません。

または、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()
}

また

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

@ianlancetaylorの意見を深く理解できます。 私たちはいつものようにfmt. Printfで生きることができることを知っています。

同時に、 @alanfoの意見にこれ以上同意することはできませんでした。

読みやすく、変更しやすく、エラーが発生しにくい

これは、堅牢なシステムプログラミングにとって非常に重要です。

他の言語の機能やアイデアを採用することは、人々の貴重な時間を大幅に節約できるのであれば、まったく恥ずべきことではないと思います。 実際、多くの人が文字列補間の問題に苦しんでいます。
他の現代言語が文字列補間を採用しているのには理由があります。

GoがCよりも少しだけ優れた言語のままであってほしくない。
Gov2はGoがはるかに優れているチャンスだと思います。

image

IMHO、文字列補間機能が提供されている場合、私たちのほとんどはそれを使用することを選択します。 感じることができます。

ps。
残念ながら、 @runeimpのケースはあまりにもエッジケースです。 状況の痛みを感じることができます。 しかし、それは提案のアイデアを曖昧にします。 (違反なし)

@doorttsは攻撃を受けません。 私はいつも正直な会話に感謝しています。

実際のコードを投稿すると、議論にへこみが生じる可能性があることを私は知っていました。 また、精神的に拡張するために少しの創造性を必要とする近似を投稿することは、おそらく議論を混乱させるだけであることも知っていました。 だから私は他の誰かがそれをすることを期待していないのでギャンブルをしました。 しかし、実際のコードを共有することは選択肢ではなく、具体的な例でこれを白黒にするのにかかる時間ですが、それ以外の場合はまったく使用しませんが、今のところ私がエネルギーを持っているわけではありません。 私はすでに前に述べた理由のためにこの議論が困難であることをすでによく知っています。 私は、この機能の真の価値を理解している12人の一人ではないことを知っています。 しかし、それらのほとんどは、ほぼ確実にこのスレッドの近くにはありません。 Redditでたまたまそれについての投稿を見たので、私はここにいるだけです。 そうでなければ、私はこのプロセスにほとんど完全に気づいていませんでした。 会話に飛び込む必要性を感じることなく、Goで物事がどのように進んでいるかにかなり満足しています。 しかし、言語の変更が提案されるたびに克服する慣性の量のために、それを求めて戦う声はそれほど多くないことを知っていたので、私はこの議論を手伝いたいと思いました。 言語の変化に対するこれほど強い反対は今まで見たことがないと思います。 そして、それは何か新しいことを支持して投稿することを非常に恐ろしいものにします。 どの言語もレビューなしですべての新機能のリクエストを受け入れるべきだと言っているのではありません。 サポート側で静かに見える理由は、必ずしも欲求がないからではありません。 それで、この機能に興味のある人は_something_を投稿してください。 何でも、これでいくつかの実数を見ることができます。

休日なので一般的にはあまり注目されていないと思います。 文字列補間は、開発者の観点からは非常に便利で望ましいものです(これが、他の多くの言語で使用されている理由です)が、この提案は、現時点ではこのような大きな変更に対応していないようです。拒否された場合、私はそうは思いません。将来、再検討する価値がないことを意味します。 これを明らかにする小さな増分変更はまだ実施されていません。

スコープから名前で変数を動的に解決することは、現在可能だと私が思うことではありません(yaegi Evalはそれを行うようですが?)。

@runeimpが参照している投稿はr/golangの私の投稿だと思いますhttps://www.reddit.com/r/golang/comments/d1199a/why_is_there_no_equivalent_to_f_strings_in_python/
もう少し議論があるところ。

私はただ帽子をかぶって、可変補間が私にとって非常に役立つだろうと言いたかったのです。Pythonの世界から来たfmt.sprintfのようなものは、Goをひどく不格好で読み/維持するのが恐ろしいように見えます。

Goは、非常に簡単で強力なことを行う方法において、ほとんどの場合非常にエレガントです。 読みやすさと保守性はその哲学の中核となるテナントの一部であり、文字列を読んで何が入っているのかを知ることはそれほど難しいことではありません。 どの変数がその文字列の最後にあるリスト内のどの項目にマップされているかを念頭に置きながら、何が出力されるかを理解することで、非常に重要なオーバーヘッドが発生します。 その精神的なオーバーヘッドを維持する必要はありません。

fmt.sprintfが適切であると思われる場合は、それを使用できます。 提案が現在の方法よりも間違いなく読みやすく、直感的で、保守が容易であるため、両方が言語としてのGoを損なうとは感じません。 これは、50以上の変数の補間によって正当化される必要はありません。これは、1つの変数だけの補間による改善です。

のようなものです

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

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

言語に本当に有害な見通し? 間違っていることが証明されてうれしいですが、正直なところ、この(またはこのような)提案では、逆方向の非互換性を除いて、逆さまにしか見えません。これは、Goの新しいメジャーバージョンでこれを実装するようなものが理にかなっている場合です。

カットオフが提案されたとき、コミュニティのかなりの部分が感謝祭、クリスマス、新年のために休暇をとっていたので、この議論をもう1セット4週間開いたままにしておくことを検討できますか?

https://github.com/golang/go/issues/34174#issuecomment -558844640以降、さらに多くの議論が行われているため、これをfinal-comment-periodから戻します。

ここで説得力のある例をまだ見ていないという@randall77に同意する傾向があります。 @runeimp 、サンプルコードを投稿していただきありがとうございますが、読みにくく、どちらの方法でも変更するのは難しいようです。 @egonelbreが示唆しているように、これをより保守しやすくしたい場合、最初のステップはまったく異なるアプローチを見つけることのようです。

@cyclingwithelephants fmt.sprintf("I am %T{age} years old")はここのテーブルにはありません。 この言語は、 fmt.Sprintfがageを解決するために使用できるメカニズムを提供していません。 Goはコンパイルされた言語であり、ローカル変数名は実行時に使用できません。 おそらく上記の@bradfitzの提案に沿って、その機能を実現する方法を見つけられれば、これはより口に合うでしょう。

最終コメント期間のラベルを解除していただき、ありがとうございます@ianlancetaylor 。 😀

@bradfitzのアイデアは素晴らしかったと思います。 ローカル変数のコンテキストがなくても、潜在的な制限があると思いますが、文字列補間がない場合は、これらの制限を喜んで受け入れます。 私はGoのパーセンテージフォーマットの更新に敬意を表していますが( %q 、 %v 、および%#vの追加が大好きです)、そのパラダイムは古くからあります。 由緒あるからといって、それが最善の方法であるとは限りません。 Goのコンパイル処理方法と同じように、特にクロスコンパイルは、CまたはC++で実行する方法よりも_WAY_優れています。 さて、Goはすべての厄介なコンパイラオプションを適切なデフォルトで非表示にしますか?それは同じように醜い内部です。 具体的にはわかりませんが、そうだと思います。 そして、それは問題ありません。 それは私には完全に受け入れられます。 機能を機能させるためにコンパイラがどのような暗い儀式を行うかは気にしません。 この機能が生活を楽にしてくれることを私は知っています。 そして、それをサポートする私が使用したすべての言語の開発者としての私の生活を楽にしてくれました。 そして、それをサポートしていない言語では常に苦痛です。 必要な文字列フォーマットの98%に対して、パーセントフォーマットで30以上の特殊文字を使用する方法よりも覚えやすいです。 また、「正しいパーセンテージ形式」の代わりに%vを使用すると言っても、作成の使いやすさは同じではなく、メンテナンスのしやすさも同じではありません。

もう少し時間がありますので、例を挙げて、人間とのインターフェースを扱う私たちの作業効率に対する重要な利点を説明するのに役立つ、これまでよりも少し啓発的な記事を見つけることができるかどうかを確認します。 、ドキュメントとコードの生成、および定期的な文字列操作。

これは、何かを実装できるようになるかもしれない考えです。 いい考えかどうかはわかりませんが。

新しい文字列型m"str"を追加します(おそらく生の文字列と同じです)。 この新しい種類の文字列リテラルは、 map[string]interface{}と評価されます。 マップで空の文字列を検索すると、文字列リテラル自体が表示されます。 文字列リテラルには、中括弧で囲まれた式を含めることができます。 中括弧内の式は、文字列リテラルに含まれていないかのように評価され、値は中括弧内に表示されるサブ文字列であるキーとともにマップに格納されます。

例えば:

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

これは印刷されます

twice i is {i * 2}
2

文字列リテラル内では、中括弧をバックスラッシュでエスケープして、単純な中括弧を示すことができます。 引用符で囲まれていない、一致しない中括弧は、コンパイルエラーです。 中括弧内の式をコンパイルできない場合も、コンパイルエラーです。 式は正確に1つの値に評価される必要がありますが、それ以外の場合は制限されません。 同じブレース文字列が文字列リテラルに複数回表示される場合があります。 表示される回数だけ評価されますが、評価の1つだけがマップに保存されます(すべて同じキーを持つため)。 正確にどちらが格納されるかは指定されていません(これは、式が関数呼び出しの場合に重要です)。

このメカニズム自体は独特ですが、役に立たないものです。 その利点は、明確に指定でき、おそらく言語に過度の追加を必要としないことです。

使用には追加機能が付属しています。 新しい関数fmt.Printfmはfmt.Printfとまったく同じように機能しますが、最初の引数はstringではなく、 map[string]interface{}なります。 マップの""値は、フォーマット文字列になります。 フォーマット文字列は、通常の%に加えて、新しい{str}修飾子をサポートします。 この修飾子を使用すると、値の引数を使用する代わりに、 strがマップで検索され、その値が使用されます。

例えば:

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

20個のスペースに渡された文字列hiを出力します。

当然、より単純なfmt.Printmがあり、 fmt.Printによって出力される、含まれている値を各ブレース式の代わりに使用します。

例えば:

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

印刷します

i: 1; j: 2

このアプローチの問題点:文字列リテラルの前にmプレフィックスを使用するという奇妙なこと。 通常の使用で複製されたm -括弧の前後に1つずつ。 m文字列を期待する関数と一緒に使用しない場合のm文字列の一般的な無用。

利点:指定するのはそれほど難しくありません。 単純な補間とフォーマットされた補間の両方をサポートします。 fmt関数に限定されないため、テンプレートまたは予期しない使用で機能する可能性があります。

型付きマップ(runtime.StringMapなど)の場合、途切れ途切れのPrintfmを追加せずにfmt.PrintとPrintlnを使用できます。

定義された型を使用することは良い考えですが、 fmt.Printfmには役立ちません。 定義された型をfmt.Printfの最初の引数として使用することはできませんでした。これは、 stringしか必要としないためです。

@runeimpによってスレッドで以前に言及されたが、完全には議論されていないことの1つは、 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.
}

これは単純なケースには冗長すぎますが、補間する値の数が多い場合ははるかに口当たりが良くなります(ただし、どのアプローチでも70の値に問題があります!)。 利点は次のとおりです:-

  1. マッピング関数にクロージャを使用すると、ローカル変数を適切に処理できます。

  2. また、任意のフォーマットを適切に処理し、補間された文字列自体からそれを排除します。

  3. マッピング関数に存在しない補間文字列でプレースホルダーを使用すると、自動的に空の文字列に置き換えられます。

  4. マッピング機能の変更は比較的簡単です。

  5. 私たちはすでにそれを持っています-言語やライブラリの変更は必要ありません。

@ianlancetaylorその解決策は私には確かな選択肢のように聞こえます。 なぜ別のPrintメソッドが必要なのかわかりませんが。 私は何かを見落としている可能性がありますが、 interface{}とタイプチェックを使用した単純な署名の変更のようです。 OK、署名の変更が既存のコードにとって非常に問題になる可能性があることに気づきました。 ただし、基本的なメカニズムが実装されていて、$ stringまたはm"str"を表す$ stringlitタイプも作成した場合、またはm"str"もstringを受け入れた場合

@alanfoを再び取り上げてくれてありがとう、それらはすべて素晴らしい点です。 😃

私はライトテンプレートにos.Expandを使用しましたが、とにかく値のマップを作成する必要がある状況では非常に便利です。 ただし、マップが不要で、(現在は何度もコピーされている)置換関数のローカル変数をキャプチャするためだけに、いくつかの異なる領域でクロージャを作成する必要がある場合、置換関数はDRYを完全に無視し、メンテナンスの問題を引き起こし、追加するだけです。より多くの作業が補間された文字列は「正しく機能」し、それらのメンテナンスの問題を軽減し、その動的な文字列を管理するためだけにマップを作成する必要はありません。

@runeimp fmt.Printfの署名を変更することはできません。 それはGo1の互換性を壊します。

stringlit型の概念は、Goの型システムを変更することを意味します。これははるかに大きな問題です。 Goは意図的に非常にシンプルな型システムを採用しています。 この機能のためにそれを複雑にしたいとは思わない。 そして、たとえそうしたとしても、 fmt.Printfはstring引数を取ることになり、既存のプログラムを壊さずにそれを変更することはできません。

@ianlancetaylor説明してくれてありがとう。 fmtパッケージや型システムのような基本的なものとの下位互換性を壊したくないという願望に感謝します。 どういうわけかそれらの線に沿ったオプションであるかもしれないいくつかの隠された(私にとって)可能性があるかもしれないことを私はただ望んでいました。 👼

私はこれを実装するIanの方法が本当に好きです。 ジェネリックは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 { 
  // ...
}

このようにして、Go1の互換性を維持する必要があります。

Go 1との互換性のために、関数のタイプを変更することはできません。 関数は呼び出されるだけではありません。 それらは次のようなコードでも使用されます

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

関数のテーブルを作成するとき、またはテストに手動の依存性注入を使用するときに、人々はこのようなコードを記述します。

私はstrings.Replacer(https://golang.org/pkg/strings/#Replacer)が文字列補間をほぼ実行できると感じていますが、補間識別子($ {...}など)とパターン処理(たとえばvar i int = 2の場合、「$ {i + 1}」はリプレースメントの「3」にマップする必要があります)

さらに別のアプローチには、fmt.Sprintf( "I am a%s%d"、foo、バー)。 少なくとも、それは完全に下位互換性があります、FWIW。

言語設計の観点からは、組み込み関数を標準ライブラリ内の関数への参照に拡張するのは独特です。 言語のすべての実装に明確な定義を提供するには、言語仕様でfmt.Sprintfの動作を完全に定義する必要があります。 避けたいと思います。

これはおそらくみんなを幸せにするわけではありませんが、以下が最も一般的だと思います。 それは3つの部分に分かれています

  1. フォーマット文字列とmap[string]interface{}を受け取るfmt.Printm関数
  2. #12854を受け入れると、呼び出すときにmap[string]interface{}をドロップできます
  3. "name": name,または"qual.name": qual.name,の省略形として、マップリテラルでキーなしの名前を許可します

一緒に取られて、それは次のようなものを可能にするでしょう

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

それでもフォーマット文字列と引数の間に重複がありますが、ページ上ではるかに軽く、簡単に自動化できるパターンです。エディタまたはツールは、文字列とコンパイラに基づいて{i, j}を自動的に入力できますそれらが範囲内にないかどうかを通知します。

それでは、フォーマット文字列内で計算を行うことはできません。これは素晴らしいことですが、それをボーナスと見なすのに十分な回数やり過ぎているのを見てきました。

一般にマップリテラルに適用されるため、他の場合にも使用できます。 作成しているマップにあるキーにちなんで変数に名前を付けることがよくあります。

これの欠点は、構造体のキーが解除される可能性があるため、構造体に適用できないことです。 これは、 {:i, :j}のような名前の前に:を要求することで修正できます。そうすれば、次のことができます。

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

これには言語サポートが必要ですか? 現在のように、マップタイプまたは流動的でよりタイプセーフなAPIのいずれかを使用すると、次のようになります。

package main

import (
    "fmt"
    "strings"
)

type V map[string]interface{}

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

type Buf struct {
    sb strings.Builder
}

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

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

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

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

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

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

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

まだ非効率的ですが、これをサポートするパッケージを作成するのはそれほど手間がかからないようです。 ボーナスとして、補間をある程度シミュレートすると言われる可能性のある別の流動的なAPIを含めました。

@ianlancetaylorからの「マップ文字列」の提案(私は個人的には「値/変数文字列」とav「...」構文を好みますが)、フォーマット以外のユースケースも許可します。 たとえば、#27605(演算子のオーバーロード関数)は主に存在します。これは、今日、 math/bigやその他の数値ライブラリ用の読み取り可能なAPIを作成することが難しいためです。 この提案は機能を可能にするでしょう

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

使用されます

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

重要なことに、このヘルパー関数は、現在存在するよりパフォーマンスが高く強力なAPIと共存できます。

このアプローチにより、ライブラリは大きな式に対して必要な最適化を実行できます。また、値をGo変数として表現しながらカスタム式の解析が可能になるため、他のDSLにも役立つパターンになる可能性があります。 特に、これらのユースケースは、囲まれた値の解釈が言語自体によって課されるため、Pythonのf文字列ではサポートされていません。

@HALtheWiseありがとう、それはかなりきちんとしています。

一般的な開発者の立場から、この提案に対する少しの支持を示すためにコメントしたいと思います。 私はプロとして3年以上golangでコーディングしてきました。 (obj-c / swiftから)golangに移動したとき、文字列補間が含まれていないことに失望しました。 私は過去10年以上CとC++を使用してきたので、printfは少し後退したいという気持ちを除けば、特に調整する必要はありません。コードの保守と読みやすさには確かに違いがあることがわかりました。より複雑な文字列の場合。 私は最近(gradleビルドシステム用に)少しkotlinを実行しましたが、文字列補間を使用することは新鮮な空気の息吹でした。

文字列補間は、言語に不慣れな人にとって文字列構成をより親しみやすくすることができると思います。 また、コードの読み取りと書き込みの両方で認知的負荷が軽減されるため、技術的なUXとメンテナンスにもメリットがあります。

この提案が真に検討されていることをうれしく思います。 提案の解決を楽しみにしています。 =)

私が正しく理解していれば、 @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,
}

その後、印刷関数がそのマップを処理し、テンプレート全体を再度解析し、事前に解析されたテンプレートのいくつかの利点を活用します

しかし、m "str"を関数に展開するとどうなるでしょうか?

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
)

この関数には次のシグネチャがあります。

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

この関数は、事前に解析されたテンプレートをより活用するために、Rustがprintln!関数で行うのと同様に、はるかに多くの最適化を実行できるため、パフォーマンスが向上します。

ここで説明しようとしているのは、Javascriptのタグ付き関数と非常によく似ており、コンパイラが文字列をフォーマットするためにユーザー関数を受け入れる必要があるかどうかを議論できます。

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

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

@rodcorsi私があなたの提案を正しく読んでいる場合、コンパイラは%20sの開始位置と終了位置を理解する必要があるため、適切な言語にfmt.Printfフォーマットを組み込む必要があります。 それは私が避けようとしていたことの1つです。

また、私の提案はfmt.Printfのフォーマットとはまったく関係がなく、他の種類の補間にも使用できることに注意してください。

m"..."を関数呼び出しに拡張するものとして扱うのは反対です。これは、実際に何が起こっているのかがわかりにくくなり、関数呼び出しの2番目の構文が効果的に追加されるためです。 一般に、マップよりも構造化された表現を渡すことは合理的であるように思われます。これは、どこでも解析動作の一致する再実装が必要になるのを避けるためです。 おそらく、一定の文字列セクションのスライス、中かっこで囲まれた文字列のスライス、およびインターフェイススライスを含む単純な構造体ですか?

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

2番目と3番目のスライスは同じ長さである必要があり、最初のスライスは1つ長くなければなりません。 これを表現して、その制約を構造的にエンコードする方法は他にもあります。
これが元の文字列を直接公開する形式に勝る利点は、それを使用する関数に正しくパフォーマンスの高いパーサーを含める必要が緩いことです。 エスケープ文字またはネストされたm文字列がサポートされていない場合、それはおそらく大したことではありませんが、そのパーサーを再実装してテストする必要はなく、その結果をキャッシュするとランタイムメモリリークが発生する可能性があります。

「フォーマットオプション」がこの構文を使用することを頻繁に望んでいる場合、仕様にそれらの場所があることがわかりますが、個人的には、コンパイラがすべてを渡すm"{name} is {age:%.2f} years old"のような構文を使用します関数への:の後。

こんにちは私はこの提案にサポートを追加するためにこれについてコメントしたいと思いました。 私は過去5年間、さまざまな言語(Kotlin、Scala、Java、Javascript、Python、Bash、一部のCなど)を使用しており、現在Goを学習しています。

文字列補間は、型推論と同じように、現代のプログラミング言語では必須だと思います。Goでもそうです。

Sprintfで同じことを達成できると主張する人にとっては、Goで型推論がある理由がわかりませんが、型を正しく記述して同じことを達成できますか? ええ、そうですが、ここでのポイントは、文字列補間によって、それを達成するために必要な冗長性が大幅に削減され、読みやすくなることです(Sprintfを使用すると、引数リストと文字列にジャンプして、意味を理解する必要があります。文字列)。

実際のソフトウェアでは、これは非常にありがたい機能です。

Goのミニマリストデザインに反対ですか? いいえ、それはコードを複雑にする(継承のような)クレイジーなことや抽象化を可能にする機能ではなく、コードを読むときに書く量を減らして明確にする方法であり、Goが何であるかに反しないと私は信じていますやろうとしています(型推論があり、:=演算子などがあります)。

静的型付けを使用する言語に対して正しくフォーマットされます

Haskellには、文字列補間用のライブラリと言語拡張機能があります。 別にタイプじゃない。

このページは役に立ちましたか?
0 / 5 - 0 評価