Go: 提议:Go 2:添加一个三元条件运算符

创建于 2019-07-18  ·  78评论  ·  资料来源: golang/go

我不同意这里的 Go 约定和语言的设计者论点https://golang.org/doc/faq#Does_Go_have_a_ternary_form并认为这是该语言中真正缺失的功能。

在 C 中考虑以下代码:

printf("my friend%s", (nbFriends>1?"s":""));

或在 C++ 中:

std::cout << "my friend" << (nbFriends>1?"s":"") << std::endl;

在 Go 中,它会导致可能导致错误的巨大重复,或者非常冗长和低效的代码,或者两者兼而有之,因为这应该是直截了当的:

解决方案1:

// horribly repetitive, risk of divergence between the two strings
if nbFriends > 1 { 
  fmt.Printf("my friends\n") 
} else { 
  fmt.Printf("my friend\n")
}

解决方案2:

// difficult to read
fmt.Printf("my friend")
if nbFriends > 1 { fmt.Printf("s") }
fmt.Printf("\n")

解决方案3:

// difficult to read
var plural = ""
if nbFriends > 1 { plural = "s" }
fmt.Printf("my friend%s\n", plural)

解决方案4:

// dangerous (ifTrue and ifFalse are both evaluated, 
// contrary to a real ternary operator), 
// and not generic at all (works only for strings)
func ifStr(condition bool, ifTrue string, ifFalse string) string {
  if condition { 
    return ifTrue
  }
  return ifFalse
}
fmt.Printf("my friend%s\n", ifStr(nbFriends > 1, "s", ""))

解决方案 5:

// horrible to read, probably inefficient
fmt.Printf("my friend%s\n",
        func(condition bool) string {
            if condition {
                return "s"
            }
            return ""
        }(nbFriends > 1))
Go2 LanguageChange Proposal Proposal-FinalCommentPeriod

最有用的评论

我认为这个例子有点糟糕,因为它只涉及一个字符,在我看来并不是真正可读的 (?:"s":"")
但我确实同意应该添加三元运算符,这对我来说是显而易见的
我编了一些对我个人有用的例子,我认为你可以对其中的一些有所了解。

const PORT = production ? 80 : 8080
代替

const PORT = -1
if production {
    PORT = 80
else {
    PORT = 8080
}

fmt.Printf("Running %s build!", production ? "Production" : "Debug") .. etc

但是当然三元运算符非常难学。

所有78条评论

@gopherbot , 添加标签 Go2, LanguageChange

另请参见 #31659 和 #32860。

正如您所指出的,此决定已包含在常见问题解答中。 如果您想争论应该更改常见问题解答的答案,您需要的不仅仅是几个示例。 我向你保证,我们已经看过并考虑过这些例子。 您需要的是数据:具有代码的真实程序,通过添加条件运算符将变得更简单、更易于阅读。 您还需要反对有关条件运算符的常见问题的论据,例如它使代码更难阅读,尤其是在嵌套时。

另外,一个小问题,但你的例子不是很好,因为它只适用于英语,不支持消息字符串的本地化。

@ianlancetaylor

在 #31659 中,您提出了我认为非常好的反建议,即使用内置的cond函数来提供三元功能。 这需要是内置的(与通用函数相反),以启用对真/假参数的短路评估。 它仍然受到人们可以嵌套cond函数的可能性的影响,尽管我个人并不认为这是一个致命的问题,因为即使他们这样做了,它仍然应该比 C 的三元运算符本身的象形文字更具可读性.

由于该提案现已结束,您是否打算进一步追求该建议,或者您是否完全放弃了拥有替代三元形式的想法?

我个人不打算进一步推动这个想法。 这更像是一个讨论的想法,而不是一个严肃的建议。 当然,如果有人想把它打磨成一个真正的提案,我不介意。 但为了被接受,我认为我们仍然需要查看一些数据,说明它将在多大程度上简化实际现有程序。

好的,谢谢你的澄清。

只需查看其他 C 家族语言中的代码即可了解三元运算符的常见程度,但分析 Go 代码本身会很困难,因为正如@Phrounz在他的开篇文章中指出的那样,许多结构用于工作围绕它的缺席。

使用cond的想法,他的例子会变成:

fmt.Printf("my friend%s\n", cond(nbFriends > 1, "s", ""))

说了这么多,如果我们得到泛型,我个人会满足于编写我自己的cond函数,并且鉴于缺乏短路,只在参数评估成本低廉的情况下使用它。

在我看来,编写更多代码(仅几行代码)比弄清楚x ? a : b的规则要好。 if 语句可能看起来很冗长(不确定)但很容易理解。

此外,当人们编写多个嵌套的x ? a : b时,很容易滥用三元条件运算符。 引入它的好处还不够大。

我看到三元运算符仅在单行函数中易于可视化。 即便如此,大多数时候他们处理错误处理,在这种情况下,最好坚持“缩进越少越好”的方法,方法是将错误或函数的最不可能路径包装在 if 中并随后处理,而不是具有多个分支逻辑。

我认为这个例子有点糟糕,因为它只涉及一个字符,在我看来并不是真正可读的 (?:"s":"")
但我确实同意应该添加三元运算符,这对我来说是显而易见的
我编了一些对我个人有用的例子,我认为你可以对其中的一些有所了解。

const PORT = production ? 80 : 8080
代替

const PORT = -1
if production {
    PORT = 80
else {
    PORT = 8080
}

fmt.Printf("Running %s build!", production ? "Production" : "Debug") .. etc

但是当然三元运算符非常难学。

是的,这是一个很好的例子,特别是如果它是顶级的:

const production = true

//...

const PORT = production ? 80 : 8080

因为你不需要init函数来初始化 PORT。

内置的cond函数_可能_可以用作const初始化程序,尽管通用版本绝对不能。

@Terottaja @alanfo

我认为该特定问题的惯用解决方案是使用Build Constraints

@Terottaja

有关全局变量/常量的解决方案,请参阅我的其他评论

对于局部变量/常量,我认为编写这种代码和平的惯用方式:

const PORT = -1
if production {
    PORT = 80
else {
    PORT = 8080
}

是:

PORT := 8080
if production {
    PORT = 80
}

您可能会争辩说我将const更改为var ,但如果编译器不够聪明,我会感到惊讶 ™弄清楚PORT是恒定的,因此 IMO 它对实际代码没有影响。

尽管在这个特定示例中 PORT 是const还是var可能并不重要,但更一般地说它可能很重要。 例如,如果您要声明某个整数,该整数将用于定义数组的大小。

或许我应该对这个提议表明我自己的立场。 虽然我个人对“标准”三元运算符没有任何问题,但我不确定以这种形式将它引入 Go 是否是个好主意。 我更喜欢内置的cond ,尽管它更具可读性,但实际上,我认为两者都没有被采用的可能性。

@Terottaja

有关全局变量/常量的解决方案,请参阅我的其他评论

对于局部变量/常量,我认为编写这种代码和平的惯用方式:

const PORT = -1
if production {
  PORT = 80
else {
  PORT = 8080
}

是:

PORT := 8080
if production {
  PORT = 80
}

您可能会争辩说我将 _const_ 更改为 _var_,但如果编译器不够聪明 ™_ 发现 _PORT_ 是常量,因此 IMO 对实际代码没有影响,我会感到惊讶。

我只是在谈论如果你的代码中有很多这种东西,在这种情况下三元运算符肯定会更干净,只是我的看法

在我看来,编写更多代码(仅几行代码)比弄清楚x ? a : b的规则要好。 if语句可能看起来很冗长(不确定)但很容易理解。

此外,当人们编写多个嵌套的x ? a : b时,很容易滥用三元条件运算符。 引入它的好处还不够大。

总会有人滥用它,代码可能会被滥用,这是不可避免的,但这会影响你吗? 在大多数情况下,没有

我支持此功能,虽然它可能导致代码滥用,但它确实有利于编译器优化,当您避免使用泛型 if /else 并将其替换为三元运算符时。
人们从一开始就一直在进行按位移位(谁可以责怪他们,仍然没有人建议让我们取消按位移位的可读性),而三元运算符对于今天来说至关重要。

@Lexkane :编译器已经进行了使用条件移动的优化。 我们不需要语言结构来强制进行此类优化。 例如,以下代码使用一个:

func f(x, y int) int {
    r := 3
    if x < y {
        r = 7
    }
    return r
}

如果您有没有生成条件移动的特定实例,并且您认为它应该生成,请打开代码问题。

自从我在工作中同时使用 Go 和 Javascript 以来,我无数次想在 Go 程序中编写x ? a : b ! 我应该把它写下来向@ianlancetaylor展示所有这些案例! 这一切都是真实的节目。
三元运算符是我们在学校(甚至在大学)都学过的运算符,因此它的经典形式是编写和阅读代码的自然方式。
我们都知道有三种类型的运算符:一元、二元和三元。 Go 无缘无故缺少一种类型的 IMO。
x ? a : b的双手。

我们都知道有三种类型的运算符:一元、二元和三元。 Go 无缘无故缺少一种类型的 IMO。

不过,没有理由必须有。 “三元”只是一个英文单词,意思是“由四个部分组成”。 你也可以很容易地拥有四元或五元运算符。

就个人而言,我觉得三元运算符默认阅读起来很烦人。 使用一元和二元运算符,您可以轻松查看所有内容的确切位置,但使用三元运算符,并不总是清楚什么是什么,尤其是当您开始嵌套它们时。 我可以看到他们在特定情况下更清洁的论点,但在这些情况之外,它们几乎总是更糟。 gofmt可能会有所帮助,但前提是它对重新格式化代码的方式比它更积极。 也许可以引入某种有限的方法,比如不允许嵌套或链接它,但那时我不确定它是否真的值得。

已经说过,使用最简单的一组运算符可以搞砸。 确实,Go 代码比 Java 或 Javascript 代码更易于读写。 但是让它不可读也不是不可能的。
所以三元运算符主要用于单行,应该用于这些情况。
否则你可以使用“if - then - else”,嵌套几次,让你的代码一团糟。 这总是取决于你。
我认为人们低估了代码中出现单行表达式的频率。 有时它们很少见,但有时它们填充了编写的代码的一半,在以后的情况下,我希望最好采用 Javascript 形式的三元运算符。

我喜欢在 Go 中,控制流通常是用语句完成的,而不是表达式(函数调用是明显的例外)。 许多流行的语言都力求“一切都是一种表达”,这通常很有趣,但 imo 鼓励编写“聪明”的代码。 去,对我来说,就是要尽量减少聪明。

顺便说一句,实现三元表达式的另一种(粗略)方法是:

map[bool]string{true: "", false: "s"}[nbFriends == 1]
map[bool]string{true: "", false: "s"}[nbFriends == 1]

不错的技巧,但比简单的知名广告明显? :更“聪明”。

一些经典功能可能有害,但我们已经习惯了。 我们甚至没有意识到这一点。

条件必须是布尔型,所以有时我们不得不写

x := 0
y := x != 0 ? 1 : 2

这不直观。 因为在if语句中你第一眼看到if并且你知道会有一个条件。

在三元表达式中,只有看到?才会知道这是一个条件。 当情况复杂时,您会感到惊讶并再次阅读条件。

它打破了从左到右、从上到下的阅读流程。

您可能会争辩说我将 _const_ 更改为 _var_,但如果编译器不够聪明 ™_ 发现 _PORT_ 是常量,因此 IMO 对实际代码没有影响,我会感到惊讶。

好吧,它确实会因为失去 const-ness 而有所作为。 Const 不仅仅是优化。 再往下的代码现在可能会弄乱值

怎么样:低三元但不允许嵌套它们。
我想要那个。

something ? foo : bar _seems_ 可读的唯一原因是我们已经习惯在其他语言中使用它。 但绝不比这更易读或更清晰

if something {
  foo
} else {
  bar
}

特别适合以 Go 为第一语言的新手。

如果可读性不是更好,那么唯一可能的收获就是编写更少的代码行。 对我来说,这似乎不是一个足够好的理由来为我们已经拥有的东西引入一个不太直观的替代结构。
我们已经有了if 。 为什么要添加另一种不太直观的方式来做同样的事情?

if something {
  foo
} else {
  bar
}

'else' 也会破坏视线和可读性(您必须返回并再次阅读 if 条件)。 我最好去掉“else”以支持“?”。

something ? foo : bar _seems_ 可读的唯一原因是我们已经习惯在其他语言中使用它。 但绝不比这更易读或更清晰

if something {
  foo
} else {
  bar
}

嗯,它在几个方面更清楚:

  • 它减少了本质上是微不足道的操作的仪式,
  • 它在一行中表达了一项任务,
  • 它允许我们将变量保持为“const”(从而减少精神负担和以后弄乱它的可能性),
  • 它少了 5 行代码,因此更容易获取更多代码。

特别适合以 Go 为第一语言的新手。

为什么一种语言会将其可表达性集中在新来者身上? 这就像为计算机文盲制作一个简单的用户界面,从而使任何超出某个点而错过额外功能的人感到沮丧。

@bugpowder三元表达式是完全可替换的, if语句更直观、更具表现力和更常见。

@bugpowder三元表达式是完全可替换的, if语句更直观、更具表现力和更常见。

类似的情况呢?

println("Example bool is: ", bool ? "true" : "false")
这对我来说真的很闪光,除非你希望你的代码看起来像这样,否则你真的不能使用if语句:

bool := "false" if bool { bool = "true" } println("Example bool is: ", bool ? "true" : "false")

something ? foo : bar _seems_ 可读的唯一原因是我们已经习惯在其他语言中使用它。 但绝不比这更易读或更清晰

if something {
  foo
} else {
  bar
}

特别适合以 Go 为第一语言的新手。

如果可读性不是更好,那么唯一可能的收获就是编写更少的代码行。 对我来说,这似乎不是一个足够好的理由来为我们已经拥有的东西引入一个不太直观的替代结构。
我们已经有了if 。 为什么要添加另一种不太直观的方式来做同样的事情?

当我学习编码时,三元运算符起初看起来有点奇怪和复杂,但在进一步查找并看到这些示例后,例如:

true ? "it's true!" : "It's false!"
这看起来非常合乎逻辑和简单,但这只是我。 也许对其他人来说它的 suuuper 复杂且难以理解。

我不同意这里的 Go 约定和语言的设计者论点https://golang.org/doc/faq#Does_Go_have_a_ternary_form并认为这是该语言中真正缺失的功能。

但是常见问题解答的最后一句话清楚地解释了“为什么没有?:”:

一种语言只需要一个条件控制流结构。

在一行中压缩更多逻辑的能力根本没有什么新东西。 已经有一种干净的方法来改变控制流,另一种只是过度。

让我们看看三元表达式有什么问题。 在一个简单的情况下,它们都可以。

var a, b int
fmt.Println("Example bool is: ", a != 0 && b != 0 ? "true" : "false")
var a, b int
var c string
if a != 0 && b != 0 {
        c = "true"
} else {
        c = "false"
}
fmt.Println("Example bool is: ", c)

当事情变得复杂时(添加一个条件),三元表达式看起来模棱两可。

但是,if-else 形式完成了它的工作。 我认为较短的形式并不总是更清晰的形式。

var a, b, c int
fmt.Println("Example bool is: ", a != 0 && b != 0 ? c != 0 ? "true" : "false" : "false")
var a, b, c int
var d string
if a != 0 && b != 0 {
        if c != 0 {
                d = "true"
        } else {
                d = "false"
        }
} else {
        d = "false"
}
fmt.Println("Example bool is: ", d)

三元表达式只是 if 语句的一种特殊情况。 为简单的特殊情况添加新语法是不值得的。

是的,请,是的! 我喜欢 Go,它专注于可读性,但它的代价是非常冗长的代码。
我坚信三元运算符会在不损害可读性的情况下提高“可写性”。 它只会让 Go 的代码更加优雅和方便。

说到歧义和其他编码问题,我想说的是,编码习惯不好的开发人员无论如何都会在没有三元运算符的情况下编写糟糕的代码。

@ziflex恕我直言,避免歧义的最佳编码实践是即使在具有三元表达式的语言中也不使用三元表达式。

您必须避免一些陷阱,因此您必须学习编码实践,例如

  1. 不要嵌套它。
  2. 仅在非常简单的情况下使用它。
  3. 当逻辑变得复杂时,将其重写为 if-else 形式。
    ...

这些陷阱会给你带来开销。

@DongchengWang其中一些做法可以很容易地应用于简单的“if”语句。

许多响应者说三元是可替代的,并没有带来任何新的东西。

但同时我看到有多少人使用“if err := maybeError(); err != nil {}” 结构,也可以很容易地用简单的“if”语句重写。 但由于某种原因,没有人真正抱怨它。 为什么? 因为方便。 可能是因为它从一开始就在语言中。

我们争论它的唯一原因是因为运营商从一开始就不在那里。

就个人而言,在某些情况下,使用三元表达式会使代码看起来更干净,嵌套更少。 虽然只有几个,所以我不喜欢这个......

为了通过使用或多或少的标准?:字符来避免语法的额外复杂性,在for循环的情况下重用现有语言关键字怎么样?

port := 80 if production else 8080

它仍然允许你链接和滥用它,但只能尽可能多地使用传统的if/else块......

我认为你仍然应该思考这种语言诞生的原理:简单、一种明显的做事方式、正交特性和库。

我认为我们在那里的比例约为 70%。 并非一切都是完美的。

到目前为止,我喜欢 Go 的一点是它减少了团队成员之间关于编码方式的争论,关于他们每个人可能使用的代码风格(还记得 PHP 中 PSR 的各种版本吗?)。 我们,在我们的 7 人团队中,从不争论彼此的代码。 我们只专注于我们的目标。

并不是说我不喜欢三元条件运算符,而是我需要反对添加它以及诸如 Go 之类的东西,因为我不喜欢编码方式成为我们的论点之一。

不需要的糖。 我认为这是我们试图带来“在另一种语言中使用”的东西的时候之一。 感觉不到Go。

如上所述,有一个现有的常见问题解答条目解释了为什么该语言没有条件运算符。 这个问题没有强有力的支持。 向语言添加新功能永远不会让它变得更简单。 一旦有两种方式来做某事( if?: ),每个程序员通常都必须决定使用哪种形式,这不是一件好事。 一般来说,我们在这里看不到任何新的论点。

因此,这是一个可能的下降。 开放一个月以供最终评论。

对于@ziflex的观点,我不确定如果:

if bool := operation(); bool {}

与三元运算符有很大不同。 这是一个我不会乐意放弃的结构,但它有可能遭受与eval ? a : b相同的复杂性,例如:

func main() {
    if a := A(); !B(a) && !C(a) && D(C(a)) {
        fmt.Println("confused")
    }
}

func A() bool {
    return true
}

func B(in bool) bool {
    return !in
}

func C(in bool) bool {
    return in
}

func D(in bool) bool {
    return in
}

这仅用于说明单行上编写不佳的代码可能会像上面的某些示例一样变得难以阅读。 恕我直言,滥用语法的能力不应成为阻止将显着可用的运算符添加到语言中的充分理由。

但是,if-else 形式完成了它的工作。 我认为较短的形式并不总是更清晰的形式。

var a, b, c int
fmt.Println("Example bool is: ", a != 0 && b != 0 ? c != 0 ? "true" : "false" : "false")

这是一个糟糕的示例,因为您省略了括号,这使得您的示例听起来非常混乱,但可以理解。

我真心认为
go var a, b, c int fmt.Println("Example bool is: ", (a != 0 && b != 0 ? (c != 0 ? "true" : "false") : "false"))

var a, b, c int
var d string
if a != 0 && b != 0 {
        if c != 0 {
                d = "true"
        } else {
                d = "false"
        }
} else {
        d = "false"
}
fmt.Println("Example bool is: ", d)

(即使您的示例实际上可能更简单,但我认为这不是重点:)

go fmt.Println("Example bool is: ", (a != 0 && b != 0 && c != 0 ? "true" : "false"))

尽管我偶尔会发现自己想要在其他表达式或语句中使用条件,但我几乎总是觉得这将是可读性的净损失。

我对此感到遗憾的一件事是缺乏一种方法来进行条件初始化,其中两个分支都不是零值。 我知道编译器可能足够聪明,不会浪费,但我对抽象机所做的事情的感觉告诉我,我正在清零一个值,然后立即覆盖它。 这是……一个非常弱的论点,真的。

FWIW,我没有发现“示例布尔”示例的三元形式更具可读性,尤其是因为在我现在的显示器上,它们最终需要水平滚动才能看到完整的表达式。 即使这样做,我也必须更频繁地来回查看才能弄清楚它在做什么。

一些脚本语言的明显答案是让 if 语句成为可以分配的表达式,我真的很喜欢这些语言的设计,但我认为我不太喜欢 go。

我想总会有:

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

...但是想想看,如果我们可以简化它,它实际上对于这种情况非常有用。 让我们表达“这个函数实际上只是符号,不需要实际生成函数代码,我们只是想要一种在内部块中使用返回表达式的方法”......

Go 语言为简单而生。 你为什么要做这些花哨的事情? 你觉得自己写了几行代码,但它给代码增加了更多的复杂性和混乱。
所以我不支持

它不是“花哨的”,它是“简单的”,所以很适合。它很熟悉,因为我们在许多其他语言中使用它。 这很方便,因为它是一个几乎不需要输入的单行表达式。 拥有它很重要,因为它是我们会经常使用的常见构造。

然后我宁愿使用像python这样的列表理解:
a if a>1 else b
而不是各种奇怪的符号,就像 Rust 一样。
我宁愿写更多的代码来表达它,也不愿用这些奇怪的符号来省略代码。
代码供人们阅读。

请不要为不常用的情况添加新的语法函数,因为你在破坏 Go 的初衷,即简单易读。
我认为有时候,很多人是自私的,为了他们在某些情况下的便利,或者因为他们被其他编程语言提供的便利特性所宠坏,你必须在 Go 中添加他们喜欢的特性。
我想说的是,Go 不是你的语言,Go 有他自己的路要走。
尽管您可以发明自己的语言,但您可以。

@Yanwenjiepy看起来你也成为了一个围棋“纯粹主义者”,在我看来这阻碍了进步。

I would rather write more code to express it than to use these strange symbols
对于我们大多数熟悉任何常见的基于 C 语言的人来说,这可能对您来说很陌生,但对我们来说并不陌生; C、C++、C#、Java、JavaScript等。它们都有三元表达式。

Please don't add new grammar functions for a situation that is not commonly used
三元条件语句其实很方便也很常用!

只是为了挑剔,这不是“列表理解”。 列表理解具体类似于[x for y in z] (可能加上条件)。 在表达式中使用 if/else 是一个不同的特性。

我对三元运算符非常熟悉,并且在我一生中大部分时间都使用过它们,但是我在其他语言中看到的大约 95% 的用法,我认为不适合让 Go 工作愉快in. Go 倾向于避免某些类型的信息密度,例如可以在表达式中使用的前自增/后自增运算符,我认为三元运算符具有相同的潜在问题; 考虑它的作用需要太多空间。

编译器相当聪明。 您可以声明一个值,有条件地分配给它,使用它一次,并期望编译器做的和它对三元表达式所做的一样。

@Yanwenjiepy看起来你也成了一个Go“纯粹主义者”,在我看来这阻碍了进步。

I would rather write more code to express it than to use these strange symbols
对于我们大多数熟悉任何常见C语言的人来说,这可能是奇怪的,但不是奇怪的符号; C,C ++,C#,Java,JavaScript等。它们都具有三元表达式。

Please don't add new grammar functions for a situation that is not commonly used
三元条件语句实际上非常方便且常用!
Maybe, I have a similar feeling myself. Perhaps on other issues, I am not a ‘Go purist ‘’. This is very confusing.

@seebs我当然可以尊重这种观点,但是对于我们很多来自其他基于 C 的语言的人来说,“工作愉快”意味着通过熟悉和方便来提高生产力。 我永远无法理解为什么 Go 中的i++不如i = i + 1令人愉快/方便,尤其是当后增量在循环中可以使用时,例如for i := 0; i < 5; i++ {...}但它不能作为一个语句! 我说的太纯粹了! :)

你在说什么? i++完全可以作为 Go 中的语句。 不允许在 _expression_ 中使用它,在其中评估它的值副作用。

https://play.golang.org/p/m_LbSbmT1Ar

作为一个相当熟悉 C 的人,我仍然发现没有三元运算符我很好,而且我更喜欢生成的代码。 我也已经停止在 C 中使用它,就像我在所有块上使用大括号变得更加一致,而不仅仅是其中包含多个语句的块。

我的意思不是i++本身,我的意思是像fmt.Printf("%d", i++)这样的表达式,这对我们中的一些人来说会很方便。

是的,它确实很方便,但很明显,它的可维护性较差。 人们会犯错误,人们会误解使用它的代码。 它是错误的来源,它只是简单地没有增加那么多价值。

是的,如果我想做x++ ,我必须在Printf之前或之后做它自己的陈述。 这确实是一个成本,在所有。 但作为交换,我得到:

  • 如果我注释掉一些 Printf 调用,我不会突然让 x 得到错误的值。
  • 对格式消息的更改不会破坏我的程序逻辑。
  • 其他人略读代码(或者我没有足够的咖啡)不会简单地错过增量在那里。

这是一个权衡,但我认为这是一个很好的权衡。 我花了一些时间尝试回答新手编程问题,因为这是我们如何发展更健康的语言社区的一部分。 与他们的 C 代码相比,我花更少的时间试图解决人们的 Go 代码中标点符号的微妙之处,而且他们完全由细微的拼写错误引起的问题要少得多。

我向你保证,这不是来自不理解或不欣赏 C 的人的纯粹主义。这是一个经过深思熟虑的决定,这种语言似乎从简单的不那么复杂中获得了很多价值,这给我们留下了更多的空间用我们的逻辑做复杂的事情,因为我们没有花费太多精力来解析代码。

我听到了,但我并不相信所有这些。 例如,以下在 Go 中有效,根据您的论点,它应该是唯一允许的语法:

    for i:=0; i<5; i=i+1 {
      fmt.Printf("%d\n", i)
    }

但也允许使用更流行/熟悉的语法:

    for i:=0; i<5; i++ {
      fmt.Printf("%d\n", i)
    }

你认为这是为什么?

我的论点实际上并没有说明只允许第一个。 我更喜欢第二种,它更简单,更容易阅读,因为增量运算符是一个独立的东西,而不是副作用

请注意,您不能这样做:

i := 0
for i++ < 5 {
    ...
}

因为 Go 不允许您在表达式中放置赋值或增量。 有时这可能会带来不便,但它不会导致人们误解表达式并且没有意识到它修改值的频率基本上是 100%,这很好。

看到这对我来说太纯粹了 :) 无论如何,我的意思是,由于循环中允许i=i+1i++ ,我说也允许那些喜欢单行便利的人使用三元变化,例如

```去
港口:=生产? 80 : 8080

as well as the usual:

```Go
Port := 8080
if production {
    Port = 80
}

如果是关于简单,我认为前者更简单。

关于什么:

Port := production++

要么

fmt.Printf("port: %d\n", production++)

使用 C 习语,这两者也比在 Go 中更短。 但我认为,在这两种情况下,存在这种复杂性的可能性会使整个程序更难理解——你现在必须时刻注意这些影响。

在更基本的哲学层面上:问题是您的解决方案不仅适用于那些喜欢“方便”的人。 它也是通过武力强加给其他所有人的,永远。 因为每个人都必须阅读别人的代码。 所以人们不能选择退出这样的功能。 他们不能说“好吧,这对我来说更难维护,所以我不会使用它”并且不必处理它。 他们坚持认为它是他们世界的一部分。 每当他们阅读其他人都可以编写的代码时,他们必须注意一组新的复杂性。

在实践中,这种“简单性”付出了巨大的代价,因为它实际上并不是简单性,它只是让表达式更短。 这就像单行无括号if ; 看起来它更简单,但是您需要第二行,并且您忘记添加大括号的可能性非零,并且很快您就失去了比将大括号放在那里更多的时间。

我知道如果你写会发生什么:

Port := production ? 80 : 8080

几天之后:

Port := production ? 80 : test ? 4080 : 8080

但是后来有人意识到这两个布尔值是一个糟糕的选择,并修复了这个问题:

Port := mode == "production" ? 80 : mode == "test" ? 4080 : 8080

而且因为它只是一行,并且使用?: ,人们觉得让它更长是额外的努力,他们不会修复或清理它。 现在他们已经投资了。

这就是你最终得到嵌套 15 深的?:操作的方式,我在实际代码中看到过,这绝对应该是查找表。

这就是你最终得到的 ?: 操作嵌套 15 深,我在实际代码中看到过,这绝对应该是查找表。

不过,嵌套 15 层的“if-else”操作也绝对应该是查找表。

哦,当然。

但是,如果您有 15 层嵌套的 if/else 操作,并且您转换为查找表,您不会觉得您已经失去了单行解决方案的“简单性”。

问题是您的解决方案不仅适用于那些喜欢“方便”的人。 它也是通过武力强加给其他所有人的,永远

我不能再不同意了,因为事实恰恰相反! 实际上,是您的纯粹主义观点强加了您的做法,并限制了我选择速记版本的自由。 如果您不想使用它,那是您的选择,但不要限制我的选择!

如果我可以选择写一个缩短的a := "freedom"而不是var a string = "freedom"那么我应该拥有三元赋值的自由和便利。

Go 工具在标准化代码格式方面做得很好,我认为仅此一项就可以很容易地阅读其他人的代码。

对我来说,底线是,我发现三元作业更容易阅读和理解,因为它们更自然地翻译成英文。 我相信这就是为什么它在许多其他语言中如此受欢迎的原因。 对我来说:

port := production ? 80 : 8080

...转换为:“这是生产吗?如果是,端口是 80,如果不是,端口是 8080”
(一个简单、直接的单一分配,即使是嵌套的)

port := 8080
if production {
    port = 80
}

这转换为:“端口是 8080(句号)哦,但是如果这是生产,请将端口更改为 80”(第二次分配)。

第二个对我来说绝对不容易阅读。 从来没有。

如果是 ? 和 :这很困扰人们,我也会对任何替代的单行语法感到满意。

这个单行结构适用于非计算的初始值。 一种将其扩展到计算初始值的方法会很棒。

v := a; if t { v = b }        // non-computed initial value

v := f(); else if t { v = b } // f() not evaluated where t==true

遗憾的是 _go fmt_ 破坏了许多有用的单行结构。 出于这个原因,我不使用_go fmt_; 伸缩可读的紧凑代码使其可读性降低。 但那是切线的。

如果 Go 2 添加了泛型,则无需三元运算符即可实现此功能

func ternary(type T)(cond bool, vTrue, vFalse T) T { 
    if cond { return vTrue } else { return vFalse }
}

当然,如果有多个ternary调用,则使用它不会很漂亮。

至于评估,这可能是编译器级别的优化。

如果 Go 2 添加了泛型,则无需三元运算符即可实现此功能

func ternary(type T)(cond bool, vTrue, vFalse T) T { 
    if cond { return vTrue } else { return vFalse }
}

当然,如果有多个ternary调用,则使用它不会很漂亮。

至于评估,这可能是编译器级别的优化。

嗯,不,vTrue 和 vFalse 将始终被评估,我的意思是

ternary(3>2, func1(), func2())

将导致 func1() 和 func2() 的调用。 没有编译器可以知道 func2() 不需要被评估......无论如何它都不应该假设,因为它是一个基本原则,在函数调用中,总是希望在函数调用之前对参数进行评估本身。 如果 func2() 除了返回一个值之外还做了一些事情,我们希望这些事情完成,否则它将非常不可预测且难以理解。

(与真正的三元运算符相反,其中 false 的值不应该按原则评估。)
```

如果 Go 2 添加了泛型,则无需三元运算符即可实现此功能

func ternary(type T)(cond bool, vTrue, vFalse T) T { 
    if cond { return vTrue } else { return vFalse }
}

当然,如果有多个ternary调用,则使用它不会很漂亮。
至于评估,这可能是编译器级别的优化。

嗯,不,vTrue 和 vFalse 将始终被评估,我的意思是

ternary(3>2, func1(), func2())

将导致 func1() 和 func2() 的调用。 没有编译器可以知道 func2() 不需要被评估......无论如何它都不应该假设,因为它是一个基本原则,在函数调用中,总是希望在函数调用之前对参数进行评估本身。 如果 func2() 除了返回一个值之外还做了一些事情,我们希望这些事情完成,否则它将非常不可预测且难以理解。

(与真正的三元运算符相反,其中 false 的值不应该按原则评估。)

那么签名会是这样的:

func ternary(type T)(cond bool, vTrueFunc, vFalseFunc func() T) T { 
    if cond { return vTrueFunc() } else { return vFalseFunc() }
}

不过我得承认,这个实现很丑:(

由于我们宣布这可能会下降(https://github.com/golang/go/issues/33171#issuecomment-525486967),因此还有其他评论,但据我们所知,他们都没有说任何实质性的新内容。 我们同意在某些情况下?:语法会很方便,但总的来说它似乎不值得添加到语言中。

-- 对于@golang/proposal-review

所以这个决定是基于“似乎不值得添加”?
我一点也不惊讶…… 三元表达式会破坏语言的“纯度”,对吧?

语言更改始终是成本效益的权衡。 很少有明确的答案。

关于没有三元运算符周期,没有一个简单的解释。

从表面上看,拒绝实现这一基本功能类似于认为自行车速度快会很危险,因此拒绝制造高速档自行车。 有些人可能会在关于 Go 的语言架构师是对还是错的讨论中混为一谈,但我更愿意提升抽象层次并考虑语言是否应该将功能问题与可维护性问题结合起来:

如果功能 X 可能被滥用并导致代码巢穴,那么是否有充分的理由将功能 X 从语言中排除?

我认为它可能是,但不是单独的:它应该与需求和难度进行权衡。 如果一个特性需求量大且易于实现,最好通过实现该特性并提出一种禁止它的方法来解耦关注点——甚至默认禁止它。

事实上,如果需求足够高,即使是困难也是拒绝它的不好理由。 考虑 Javascript (ES2015) 中的class语法:该语言的架构师确实不想添加该功能,添加语法实际上是相当多的工作,但需求非常高。 他们做了什么? 他们添加了语法,非常清楚任何不想要该功能的组织都可以轻松地在 linting 级别禁止该语法。

考虑到需求,这是适合三元运算符的分辨率。 将这些问题解耦是合适的,并且以更可配置的方式解决它们将是最有意义的。 对于像“未使用的变量”错误这样的事情也应该发生同样的事情,这些错误会使 linting 问题变成“在程序运行之前你需要修复这件事”危机。 (是的,我知道有一个_解决方案,但它仍然应该是可配置的)

认为一种语言应该是少数架构师的产物,这些架构师与实际使用该语言的架构师没有深入的联系,这是错误的。 需要数据来证明语言的设计者是错误的是令人钦佩的,但这种分析是不必要的。 只要看看这个线程的大小:有需求。

不幸的是,忽视需求是导致竞争产品的原因:在这种情况下,这就是语言被分叉的方式。 你想被分叉吗? (要明确:这是一个预测,而不是威胁。
我绝对不会分叉一种语言。)

@dash这个问题已经结束,我不会争论它,但我想纠正我认为是虚假陈述的内容。 您是在暗示 Go 是一种语言,它是“......少数架构师的产品,这些架构师了解得更好_与实际使用该语言的人没有深入的联系_。” 这当然不是真的。 Go 团队中的每个人,当然还有“架构师”每天都在编写 Go 代码,并且自 2007 年以来一直在编写大量代码。我们几乎每天都与其他 Go 用户进行交互。 我们绝对与那些实际使用该语言的人有着深厚的联系,这就是我们——以及其他许多人。

我不是架构师之一,我大量使用该语言,而且我经常遇到这样的情况,如果三元运算符可用,我几乎肯定会使用它。 后来我读了我的代码,我想了想,我很高兴它不存在。 YMMV。

我不认为让这样的事情,或者未使用的变量警告,“可配置”会让我作为开发人员的生活更轻松。 我认为这会让我作为开发人员的生活更加艰难。

我也不是架构师之一,而且我也大量使用该语言,而且我经常遇到这样的情况,如果三元运算符可用,我几乎肯定会使用它。 后来我读了我的代码,我诅咒那些否认我们这个有用功能的人!

同样在这里,我每天都使用 Go 并且每天都需要它,我相信它会让我的代码更清晰、更健壮。

顺便说一句,在“Reword FAQ answer”的提案中

为简洁起见,使用三元运算符而不是条件语句(不是表达式);

提到“简洁”,就像它是一件坏事一样。 简洁有助于提高可读性。 可读代码的整个想法是它“直截了当”它的实际作用。 不像影响 8080 或 -1 到端口,然后在代码中稍后再影响 80,因为这是生产。

我认为 Go 现在获得三元运算符的可能性很小,不仅因为 Go 团队一直反对它,而且因为(从表情符号投票来看)大约 60% 的社区也反对它。

但是,如果 Go 最终获得泛型,我认为团队应该认真考虑在标准库中添加一个三元函数,尽管除了可能通过编译器优化之外不会出现短路。

如果他们不这样做,那么支持某种三元运算符/函数(包括我自己)的 40% 将立即编写他们自己的。 这将造成可读性和维护的噩梦,因为将选择不同的名称(Cond、IFF、Iif、Pick、Choose、Tern 等),并且它们将位于具有不同名称的包中。

如果改为将其添加到标准库中,则不会发生这种碎片化,因为支持的人都会使用标准版本,不喜欢它的人至少会知道它的作用。

func ifThen(condition bool, ifTrue,ifelse interface{}) interface{}{
如果条件{
如果为真则返回
} 别的 {
返回 ifelse
}
}

在我看来,在某些情况下关于三元运算符的讨论归结为“针对不同问题的解决方案”。

函数中缺少默认值导致人们将代码编写为:

if elementType == "" {
    elementType = "Whatever"
}
//  times X ...

人们想要简单地说:

elementType = elementType == "" ? "Whatever" : elementType
// times X ...

要么

func DoDesign( elementType string = "Whatever" )

因此,三元运算符试图解决与另一个问题相关的问题。 虽然更标准的 Go 版本确实更具可读性,但当您连续处理 4 或 5 个时,它的可读性会降低。

还需要质疑,当人们开始构建越来越多自己的“解决方案”时是否提供了可读性,如@ArnoldoR 所示。 困扰 Javascript 的问题之一是缺少功能的“解决方案”的增长,这导致一些项目左右导入 NPM 包。 像 SqlX 这样的流行包更像是 Go 中缺少功能的标志。

可读性是一回事。 但有时不得不写 20 行或多或少可以包含在 5 行中。 它堆积在任何项目上。

如果问题是三元可能被滥用来创建不可读的代码,尤其是嵌套的三元运算符,那么对其进行限制。 我认为如果三元运算符仅限于“一级”并且编译器会阻止您使用深层运算符,那么本主题中的大多数人都不会遇到问题。

我们知道人们无论如何都会滥用泛型来实现它们。 那么为什么不提供正式版,那么你可以限制它的滥用。 但是正如我们在上面看到的那样,这些疯狂的函数只会随着时间的推移而变得越来越流行,正如我们在 Javascript 社区中看到的那样。 而当精灵离开瓶子时,就再也无法控制它了。

此页面是否有帮助?
0 / 5 - 0 等级