Cの三項演算子に対応する慣用的なGoとは何ですか?


297

C / C ++(およびそのファミリーの多くの言語)では、条件に応じて変数を宣言および初期化する一般的なイディオムは、3項条件演算子を使用します。

int index = val > 0 ? val : -val

Goには条件演算子がありません。上記と同じコードを実装する最も慣用的な方法は何ですか?私は次の解決策に行きましたが、それはかなり冗長に思われます

var index int

if val > 0 {
    index = val
} else {
    index = -val
}

もっと良いものはありますか?


else部分を使用して値を初期化し、条件が変更されるか
どうか

とにかく多くのif / thenが排除されているはずです。35年前に初めてBASICプログラムを書いたときからずっと、これを使っていました。たとえば、 int index = -val + 2 * val * (val > 0);
hyc

9
@hycあなたの例は、goの慣用的なコードほど、または三項演算子を使用したCのバージョンほど読みやすくはありません。とにかく、私の知る限り、ブール値は数値として使用できないため、このソリューションをGoに実装することはできません。
ファビアン

goにそのような演算子が用意されていないのはなぜでしょうか。
Eric Wang

@EricWang 2つの理由、AFAIK:1-あなたはそれを必要としない、そして彼らは言語をできるだけ小さく保つことを望んだ。2-乱用される傾向があります。つまり、複雑な複数行の式で使用され、言語設計者はそれを好みません。
Fabien

回答:


244

指摘されているように(そして、できれば当然のことですが)、if+else実際に使用することは、Goで条件を実行する慣用的な方法です。

ただし、完全なvar+if+elseコードブロックに加えて、このスペルはよく使用されます。

index := val
if val <= 0 {
    index = -val
}

また、と同等の繰り返しの多いコードブロックがある場合は、int value = a <= b ? a : bそれを保持する関数を作成できます。

func min(a, b int) int {
    if a <= b {
        return a
    }
    return b
}

...

value := min(a, b)

コンパイラーはそのような単純な関数をインライン化するため、高速で、明確で、短くなります。


184
やあみんな、見て!三演算子をgolangに移植したところです!play.golang.org/p/ZgLwC_DHm0とても効率的です!
thwd 2013年

28
@tomwildeあなたのソリューションはかなり興味深いように見えますが、三項演算子の主要な機能の1つである条件付き評価が欠けています。
Vladimir Matveev 2013年

12
@VladimirMatveevは、値をクロージャでラップします;)
nemo

55
c := (map[bool]int{true: a, false: a - 1})[a > b]それが機能する場合でも、難読化の私見の例です。
Rick-777

34
場合はif/else慣用的なアプローチがあり、おそらくGolangはせ検討することもできif/else句は値を返します x = if a {1} else {0}。Goは、このように機能する唯一の言語ではありません。主流の例はScalaです。参照:alvinalexander.com/scala/scala-ternary-operator-syntax
Max Murphy

80

ノーゴーは他の構文は次の場合/使用して、三項演算子を持っていないです慣用的な方法。

Goに?:演算子がないのはなぜですか?

Goには3値テスト操作はありません。以下を使用して同じ結果を得ることができます。

if expr {
    n = trueVal
} else {
    n = falseVal
}

?:Goが欠落している理由は、言語の設計者が操作をあまりにも頻繁に使用して、不可解なほど複雑な式を作成するのを見たことがあるためです。if-elseフォームは、長いものの、紛れもなく明確です。言語に必要な条件付き制御フロー構造は1つだけです。

—よくある質問(FAQ)-Goプログラミング言語


1
言語デザイナーが見てきたからといって、if-elseブロック全体のワンライナーを省略していますか?そして、誰if-elseが同じように虐待されていないと言うのですか?私はあなたを攻撃していません。デザイナーによる言い訳は十分に有効ではないと感じています
Alf Moh

58

次の3項式(C)があるとします。

int a = test ? 1 : 2;

Goの慣用的なアプローチは、単にifブロックを使用することです。

var a int

if test {
  a = 1
} else {
  a = 2
}

ただし、要件に合わない場合があります。私の場合、コード生成テンプレート用のインライン式が必要でした。

私はすぐに評価される無名関数を使用しました:

a := func() int { if test { return 1 } else { return 2 } }()

これにより、両方のブランチも評価されなくなります。


インライン化されたanon関数の1つのブランチのみが評価されることを知っておくと役に立ちます。ただし、このようなケースはCの三項演算子の範囲を超えていることに注意してください。
ウルフ

1
C条件式(一般に三項演算子として知られています)には、3つのオペランドがありますexpr1 ? expr2 : expr3expr1評価結果がの場合trueexpr2が評価され、式の結果になります。それ以外の場合は、expr3評価され、結果として提供されます。これは、K&RによるANSI Cプログラミング言語セクション2.11からのものです。私のGoソリューションは、これらの特定のセマンティクスを保持します。@ウルフあなたが提案していることを明確にできますか?
Peter Boyer 2017年

私が何を念頭に置いていたかはわかりませんが、おそらくanon関数はスコープ(ローカル名前空間)を提供しますが、これはC / C ++の三項演算子には当てはまりません。このスコープの使用例を
Wolf

39

マップの三項は括弧なしで読みやすいです:

c := map[bool]int{true: 1, false: 0} [5 > 4]

なぜそれが-2になったのか完全にはわかりません...はい、それは回避策ですが、機能し、タイプセーフです。
アレッサンドロサンティーニ

30
はい、機能し、タイプセーフであり、さらにはクリエイティブです。ただし、他のメトリックがあります。Ternary opsは、if / elseと同等のランタイムです(たとえば、このS / Oポストを参照)。この応答は、1)両方のブランチが実行される、2)マップを作成する、3)ハッシュを呼び出すためではありません。これらはすべて「高速」ですが、if / elseほど高速ではありません。また、条件{r = foo()} else {r = bar()}の場合、var r Tよりも読みにくいと主張します
knight

他の言語では、複数の変数があり、クロージャー、関数ポインター、またはジャンプがある場合に、このアプローチを使用します。変数の数が増えるとネストされたifを書き込むとエラーが発生しやすくなりますが、たとえば{(0,0,0)=> {code1}、(0,0,1)=> {code2} ...} [(x> 1 、y> 1、z> 1)](疑似コード)は、変数の数が増えるにつれてますます魅力的になります。クロージャーはこのモデルを高速に保ちます。同様のトレードオフが適用されることを期待しています。
Max Murphy

私は、あなたがそのモデルのスイッチを使用すると思います。私はスイッチが自動的に壊れる方法が気に入っています。
Max Murphy

8
カシーFoeschが指摘: simple and clear code is better than creative code.
ウルフ

11
func Ternary(statement bool, a, b interface{}) interface{} {
    if statement {
        return a
    }
    return b
}

func Abs(n int) int {
    return Ternary(n >= 0, n, -n).(int)
}

これはif / elseよりもパフォーマンスが高くなく、キャストが必要ですが機能します。ご参考までに:

BenchmarkAbsTernary-8 100000000 18.8 ns / op

BenchmarkAbsIfElse-8 2000000000 0.27 ns / op


おめでとうございます。考えられるすべてのケースを処理する1行
Alexandro de Oliveira

2
これは条件付き評価を処理しないと思いますか、それともそうですか?副作用のないブランチではこれは問題ではありませんが(例のように)、副作用がある場合は問題が発生します。
Ashton Wiersdorf

7

場合は、すべてあなたのブランチが副作用を作るか、ある計算コストが高く、次が考え、意味的に保存リファクタリング:

index := func() int {
    if val > 0 {
        return printPositiveAndReturn(val)
    } else {
        return slowlyReturn(-val)  // or slowlyNegate(val)
    }
}();  # exactly one branch will be evaluated

通常、オーバーヘッドはありません(インライン化)。最も重要なのは、名前空間が1回だけ使用されるヘルパー関数で乱雑にならないことです(読みやすさとメンテナンスが妨げられます)。実例

グスタボのアプローチを単純に適用する場合は注意してください。

    index := printPositiveAndReturn(val);
    if val <= 0 {
        index = slowlyReturn(-val);  // or slowlyNegate(val)
    }

異なる動作のプログラムを取得します。場合にはval <= 0プログラムが非正の値を印刷するだろうが、それはいけません!(同様に、ブランチを逆にすると、スローな関数を不必要に呼び出すことによってオーバーヘッドが発生します。)


1
興味深い読み物ですが、グスタボのアプローチに対する批判の要点はよくわかりません。abs元のコードに(一種の)関数が表示されます(まあ、私はに変更<=します<)。あなたの例では、初期化が見られますが、これは場合によっては冗長であり、拡張される可能性があります。明確にしていただけますか。アイデアをもう少し説明してください。
ウルフ

主な違いは、どちらかの分岐ので関数を呼び出すと、その分岐が行われるべきではなかったとしても、副作用が生じることです。私の場合、関数printPositiveAndReturnは正の数値に対してのみ呼び出されるため、正の数値のみが出力されます。逆に、常に1つのブランチを実行してから、別のブランチを実行して値を「修正」しても、最初のブランチの副作用は取り消されません
2016

わかりますが、経験のあるプログラマーは通常、副作用を認識しています。その場合、コンパイルされたコードが同じであっても、組み込み関数に対するCassy Foeschの明白な解決策を好みます。それは短く、ほとんどのプログラマーには明白に見えます。誤解しないでください。私はGoのクロージャーが大好きです;)
Wolf

1
プログラマーは通常、副作用を認識しています」-いいえ。項の評価を回避することは、三項演算子の主要な特徴の1つです。
ジョナサンハートレイ

6

序文:それif elseが進むべき道であると主張することなく、言語対応の構成要素をいじって楽しみを見つけることができます。

次のIf構成はgithub.com/icza/goxbuiltinx.Ifタイプである他の多くのメソッドとともに私のライブラリで利用できます。


Goを使用すると、などのプリミティブ型を含む、任意のユーザー定義型にメソッドをアタッチできますbool基になる型boolとして持つカスタム型を作成し、条件に対する単純な型変換を使用して、そのメソッドにアクセスできます。オペランドを受け取って選択するメソッド。

このようなもの:

type If bool

func (c If) Int(a, b int) int {
    if c {
        return a
    }
    return b
}

どうすれば使用できますか?

i := If(condition).Int(val1, val2)  // Short variable declaration, i is of type int
     |-----------|  \
   type conversion   \---method call

たとえば、三進法を行うmax()

i := If(a > b).Int(a, b)

三元的なことabs()

i := If(a >= 0).Int(a, -a)

これはクールに見え、シンプルでエレガント、そして効率的です(インライン化に適しています)。

「実際の」三項演算子と比較した1つの欠点:常にすべてのオペランドを評価します。

延期された場合にのみ必要な評価を実現するための唯一のオプションは、関数(宣言された関数またはメソッド、または関数リテラル)を使用することです。

func (c If) Fint(fa, fb func() int) int {
    if c {
        return fa()
    }
    return fb()
}

それを使用する:レッツは、私たちが計算するには、これらの機能を持っていると仮定aしてb

func calca() int { return 3 }
func calcb() int { return 4 }

次に:

i := If(someCondition).Fint(calca, calcb)

たとえば、条件が現在の年> 2020である場合:

i := If(time.Now().Year() > 2020).Fint(calca, calcb)

関数リテラルを使用したい場合:

i := If(time.Now().Year() > 2020).Fint(
    func() int { return 3 },
    func() int { return 4 },
)

最後の注意:異なるシグネチャを持つ関数がある場合、ここではそれらを使用できません。その場合、シグネチャが一致する関数リテラルを使用して、それらを引き続き適用できます。

たとえばcalca()calcb()(戻り値以外に)あまりにものパラメータを持っているでしょう。

func calca2(x int) int { return 3 }
func calcb2(x int) int { return 4 }

これはあなたがそれらを使うことができる方法です:

i := If(time.Now().Year() > 2020).Fint(
    func() int { return calca2(0) },
    func() int { return calcb2(0) },
)

Go Playgroundでこれらの例を試してください。


4

eoldの答えは興味深く、創造的で、おそらく賢いです。

ただし、代わりに以下を実行することをお勧めします。

var index int
if val > 0 {
    index = printPositiveAndReturn(val)
} else {
    index = slowlyReturn(-val)  // or slowlyNegate(val)
}

はい、どちらも基本的に同じアセンブリにコンパイルされますが、このコードは、最初に変数に書き込まれた可能性のある値を返すために無名関数を呼び出すよりもはるかに読みやすくなっています。

基本的に、シンプルで明確なコードはクリエイティブコードよりも優れています。

また、Goではマップはまったく軽量ではないため、マップリテラルを使用するコードはお勧めできません。Go 1.3以降では、小さなマップのランダムな反復順序が保証されており、これを強制するために、小さなマップではメモリの面で効率がかなり低くなっています。

その結果、多数の小さなマップの作成と削除は、スペースと時間の両方を消費します。小さなマップを使用するコードがありました(2つまたは3つのキーが考えられますが、一般的な使用例は1つのエントリのみでした)。しかし、コードは非常に低速でした。デュアルスライスキー[インデックス] =>データ[インデックス]マップを使用するように書き直された同じコードよりも少なくとも3桁遅いことを話している。おそらくもっと多かった。以前は実行に数分かかっていた一部の操作がミリ秒で完了し始めました。\


1
simple and clear code is better than creative code-これはとても気に入っていますが、最後のセクションで少し混乱しdog slowています。おそらくこれも他の人を混乱させる可能性がありますか?
ウルフ

1
つまり、基本的に... 1つ、2つ、または3つのエントリを持つ小さなマップを作成するコードがありましたが、コードの実行が非常に遅くなりました。だから、多くのm := map[string]interface{} { a: 42, b: "stuff" }、そしてその後、別の関数にそれを反復処理:for key, val := range m { code here } :2つのスライス方式に切り替えた後keys = []string{ "a", "b" }, data = []interface{}{ 42, "stuff" }等を介して、その後、および反復for i, key := range keys { val := data[i] ; code here }物事が1000倍にスピードアップ。
Cassy Foesch 16

分かりました。ありがとうございます。(おそらく、この点で答え自体改善される可能性があります。)
ウルフ

1
-.- ...touché、logic ...touché...私は最終的にそれに乗ります...;)
Cassy Foesch

3

ワンライナーは、クリエーターから敬遠されましたが、彼らの立場にあります。

これは、必要に応じて、評価する関数をオプションで渡せるようにすることで、遅延評価の問題を解決します。

func FullTernary(e bool, a, b interface{}) interface{} {
    if e {
        if reflect.TypeOf(a).Kind() == reflect.Func {
            return a.(func() interface{})()
        }
        return a
    }
    if reflect.TypeOf(b).Kind() == reflect.Func {
        return b.(func() interface{})()
    }
    return b
}

func demo() {
    a := "hello"
    b := func() interface{} { return a + " world" }
    c := func() interface{} { return func() string { return "bye" } }
    fmt.Println(FullTernary(true, a, b).(string)) // cast shown, but not required
    fmt.Println(FullTernary(false, a, b))
    fmt.Println(FullTernary(true, b, a))
    fmt.Println(FullTernary(false, b, a))
    fmt.Println(FullTernary(true, c, nil).(func() string)())
}

出力

hello
hello world
hello world
hello
bye
  • 渡された関数はinterface{}、内部キャスト操作を満たすためにを返す必要があります。
  • コンテキストによっては、出力を特定のタイプにキャストすることを選択できます。
  • これから関数を返したい場合は、で示すように関数をラップする必要がありますc

ここでのスタンドアロンソリューションも優れていますが、用途によっては不明確になる場合があります。


これは間違いなく学術的ではありませんが、これはかなり良いです。
Fabien
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.