语言学
Scala 中大多数下划线使用的起源和动机是允许人们构造表达式和声明,而无需总是给出语言的每个变量(我的意思是谓词演算中的“变量”,而不是编程中的)一个名字。我们在自然语言中一直使用这个,例如,我在这句话中使用“this”来指代前一句中的一个概念,并且使用“this”来指代这句话,而不会混淆我的意思。在自然语言中,这些词通常被称为“pronouns”、“anaphors”、“cataphors”,指称“antecedent strong>”或“postcedent”,理解/取消引用它们的过程称为“anaphora”。
算法信息论
如果我们必须先命名自然语言中的每一个“事物”,然后才能引用它,类似地,为了量化每一种事物类型,就像在谓词演算和大多数编程语言中一样,那么说话就会变得非常冗长缠绕。多亏了上下文,我们可以推断出“this”、“it”、“that”等词的含义,我们很容易做到。
为什么要将这种简单、优雅、高效的交流方式限制在自然语言上呢?所以它被添加到 Scala 中。
如果我们确实试图命名每一个“事物”或“事物的类型”,句子会变得如此冗长和复杂,以至于由于冗长和引入冗余符号而变得非常难以理解>。添加到句子中的符号越多,理解起来就越困难,因此这就是为什么它是一种很好的做法,不仅在自然语言中,在 Scala 中也是如此。事实上,人们可以用 Kolmogorov 复杂性来形式化这个断言,并证明采用占位符的句子序列比不必要地命名所有内容的句子复杂度更低(除非在每个实例中名称完全相同,但这通常没有意义)。因此,我们可以肯定地说,与某些程序员的看法相反,占位符语法更简单、更易于阅读。
它在使用中存在一些阻力的原因是,如果一个人已经是一名程序员,则必须努力重新训练大脑,不要命名所有东西,就像(如果他们能记得的话)他们可能已经找到了学习首先编写代码需要相当大的努力。
示例
现在让我们更正式地看一些具体的用途:
占位符语法
表示“它”、“他们”、“那个”、“他们的”等(即代词),例如1
lines.map(_.length)
可以读作“将行映射到它们的长度”,类似地我们可以将lineOption.map(_.length)读作“将行映射到它的长度”。就复杂性理论而言,这比“对于行中的每个 'line',取 'line' 的长度”更简单 - 这将是 lines.map(line => line.length)。
与类型注释一起使用时,也可以读作“the”(定冠词),例如
(_: Int) + 1
" 整数加 1"
存在类型
表示“某种类型”(“某些”是代词),例如
foo: Option[_]
表示“foo 是一个选项某种类型的”。
高级类型参数
再一次,基本上意味着“某种类型”(“某些”是代词),例如
class A[K[_],T](a: K[T])
可以读作“A类需要一些K某种类型的 ...”
模式匹配通配符
表示“任何东西”或“随便什么”(代词),例如
case Foo(_) => "hello"
可以读作“对于包含任何内容的 Foo,返回 'hello'”,或“对于包含任何内容的 Foo,返回 'hello'”
导入通配符
表示“一切”(代词),例如
import foo._
可以读作“从 foo 导入所有内容”。
默认值
现在我把它读成“a”(不定冠词),例如
val wine: RedWine = _
“给我一杯红酒”,服务员应该给你全屋红。
下划线的其他用途
下划线的其他用法与本问答的重点无关,但我们简要讨论一下
忽略的值/参数/提取
允许我们以明确的“模式安全”方式忽略事物。例如
val (x, _) = getMyPoint
说,我们不打算使用第二个坐标,所以当你在代码中找不到用途时,不必大惊小怪。
导入隐藏
只是说“除了”(介词)的一种方式。
功能应用
例如
val f: String => Unit = println _
这是一个有趣的词,因为它在语言学中有一个确切的类比,即名词化,“使用动词、形容词或副词作为名词短语的头部,带有或没有形态转换” - 维基百科。更简单地说,就是将动词或形容词变成名词的过程。
在特殊方法名中使用
纯粹是语法问题,与语言学无关。