【问题标题】:Why the variation in operators?为什么运营商的变化?
【发布时间】:2015-08-20 21:08:18
【问题描述】:

潜伏已久,第一次发帖。

在 Scala 中,我正在寻找关于为什么首选根据类型改变运算符的优势。例如,为什么会这样:

Vector(1, 2, 3) :+ 4

确定优于:

Vector(1, 2, 3) + 4


或者:

4 +: Vector(1,2,3)

结束:

Vector(4) + Vector(1,2,3)


或者:

Vector(1,2,3) ++ Vector(4,5,6)

结束:

Vector(1,2,3) + Vector(4,5,6)


所以,这里有 :+、+: 和 ++,而单独使用 + 就足够了。我是 Scala 的新手,我会屈服的。但是,对于一种试图用其语法保持简洁的语言来说,这似乎是不必要的和混淆的。

我已经进行了很多谷歌和堆栈溢出搜索,但只发现了有关特定运算符的问题,以及一般的运算符重载。但是,没有背景说明为什么有必要将 + 拆分为多个变体。

FWIW,我可以使用隐式类重载运算符,如下所示,但我想这只会导致有经验的 Scala 程序员使用/阅读我的代码时感到困惑(和 tisk tisks)。

object AddVectorDemo {

    implicit class AddVector(vector : Vector[Any]) {
        def +(that : Vector[Any]) = vector ++ that
        def +(that : Any) = vector :+ that
    }

    def main(args : Array[String]) : Unit = {
        val u = Vector(1,2,3)
        val v = Vector(4,5,6)
        println(u + v)
        println(u + v + 7)
    }
}

输出:

向量(1, 2, 3, 4, 5, 6)
向量(1, 2, 3, 4, 5, 6, 7)

【问题讨论】:

标签: scala operators


【解决方案1】:

答案需要通过方差绕道而行。我会尽量缩短。

首先,请注意您可以向现有 Vector 添加任何内容:

scala> Vector(1)
res0: scala.collection.immutable.Vector[Int] = Vector(1)

scala> res0 :+ "fish"
res1: scala.collection.immutable.Vector[Any] = Vector(1, fish)

为什么你可以这样做?好吧,如果B extends A 并且我们希望能够在需要Vector[A] 的地方使用Vector[B],我们需要允许Vector[B] 添加Vector[A] 可以添加的相同类型的东西。但是一切都扩展了Any,所以我们需要允许添加任何Vector[Any] 可以添加的东西,这就是一切。

使Vector 和大多数其他非集合集合协变是一个设计决策,但这是大多数人所期望的。

现在,让我们尝试将向量添加到向量中。

scala> res0 :+ Vector("fish")
res2: scala.collection.immutable.Vector[Any] = Vector(1, Vector(fish))

scala> res0 ++ Vector("fish")
res3: scala.collection.immutable.Vector[Any] = Vector(1, fish)

如果我们只有一个操作,+,我们将无法指定我们指的是哪一个。我们真的可能打算这样做。它们都是非常明智的尝试。我们可以尝试根据类型进行猜测,但实际上最好让程序员明确说出他们的意思。既然有两种不同的意思,就需要有两种方式来问。

这在实践中会出现吗?随着收藏的集合,是的,一直都是。例如,使用您的+

scala> Vector(Vector(1), Vector(2))
res4: Vector[Vector[Int]] = Vector(Vector(1), Vector(2))

scala> res4 + Vector(3)
res5: Vector[Any] = Vector(Vector(1), Vector(2), 3)

这可能不是我想要的。

【讨论】:

    【解决方案2】:

    这是一个公平的问题,我认为这与遗留代码和 Java 兼容性有很大关系。 Scala 复制了 Java 的 + 用于字符串连接,这使事情变得复杂。

    这个+ 允许我们这样做:

    (new Object) + "foobar" //"java.lang.Object@5bb90b89foobar"
    

    如果我们有+ 对应List 而我们有List(1) + "foobar",那么我们应该期待什么呢?人们可能会期望List(1, "foobar")List[Any] 类型),就像我们使用:+ 时得到的一样,但是受 Java 启发的字符串连接重载会使这复杂化,因为编译器将无法解决重载问题。

    奥德斯基甚至曾经评论过:

    永远不应该对元素类型协变的集合使用 + 方法。集合和映射是不变的,这就是为什么它们可以有一个 + 方法。这一切都相当微妙和凌乱。如果我们不尝试复制 Java 的 + 用于字符串连接,我们会做得更好。但设计 Scala 时的想法是基本上保留 Java 的所有表达式语法,包括 String +。现在改变它为时已晚。

    关于this similar question 的答案有一些讨论(尽管在不同的背景下)。

    【讨论】:

    • 上述 Odersky 引用的有用上下文:scala-lang.org/old/node/5441
    • 虽然两个回答都很有见地,但我将其标记为答案,因为它让我从语言设计者本人那里得到了理由(无论我是否同意)。
    猜你喜欢
    • 2022-04-02
    • 1970-01-01
    • 1970-01-01
    • 2017-07-14
    • 2010-10-24
    • 1970-01-01
    • 2011-07-09
    • 1970-01-01
    • 2019-12-23
    相关资源
    最近更新 更多