Scala中的右联合方法有什么好处?

我刚刚开始玩Scala,刚刚学会了如何使方法成为正确的联想 (与面向对象语言常见的更传统的左联结性相反)。

起初,当我看到示例代码在Scala中列表时,我注意到每个示例总是在右侧有List:

 println(1 :: List(2, 3, 4)) newList = 42 :: originalList 

但是,即使看了一遍又一遍,我没有三思,因为我不知道(当时) ::List上的一个方法。 我只是假设它是一个运算符(同样,在Java中是运算符),而关联性并不重要。 例子代码中总是出现在右侧的事实似乎是巧合的(我认为这可能只是“首选风格”)。

现在我知道得更好了:因为::是正确联想的,所以必须这样写。

我的问题是,能够定义右联合方法的要点是什么?

纯粹是出于美学的原因,还是在某些情况下,右结合实际上可以比左结合有什么好处呢?

从我(新手)的angular度来看,我真的不知道如何

 1 :: myList 

比任何事情都好

 myList :: 1 

但这显然是一个微不足道的例子,我怀疑这是一个公平的比较。

简单的答案是,右关联性可以通过使程序员的types与程序的实际内容一致来提高可读性。
所以,如果你input' 1 :: 2 :: 3 ',你会得到一个List(1,2,3),而不是以一个完全不同的顺序返回一个List。
那将是因为' 1 :: 2 :: 3 :: Nil '实际上是

 List[Int].3.prepend(2).prepend(1) scala> 1 :: 2 :: 3:: Nil res0: List[Int] = List(1, 2, 3) 

这是两个:

  • 更具可读性
  • 更有效率(O(1)为prepend ,而O(n)为假设append法)

(提醒,从Scala编程的书中摘录)
如果在运算符表示法(如a * b )中使用某个方法,则将在左边的操作数上调用该方法,如a.*(b) – 除非方法名以冒号结尾。
如果方法名以冒号结尾,则在右操作数上调用该方法。
因此,在1 :: twoThree ,方法在twoThree上调用,传入1,如下所示: twoThree.::(1) .: twoThree.::(1)

对于列表来说,它起着追加操作的作用(该列表似乎在'1'之后附加以形成' 1 2 3 ',实际上它是1,它被列在列表之前)。
类列表不提供一个真正的追加操作,因为追加到列表花费的时间与列表的大小成线性增长,而预先考虑::需要恒定的时间
myList :: 1会尝试将myList的全部内容预先设置为“1”,这会比给myList预先设置1更长(如在“ 1 :: myList ”中)

注意:无论操作符具有什么关联性,操作数总是从左到右进行计算。
所以如果b是一个不仅仅是对一个不可变值的简单引用的expression式,那么一个::: b更精确地被视为下面的块:

 { val x = a; b.:::(x) } 

在这个块中,a仍然在b之前被评估,然后这个评估的结果作为操作数传递给b's :::方法。


为什么要在左联合方法和右联合方法之间进行区分呢?

这允许保持通常的左联合操作(' 1 :: myList ')的外观,同时实际上对右expression式应用该操作,因为;

  • 这是更高效。
  • 但是它的反向关联顺序(' 1 :: myList '和' myList.prepend(1) ')更具可读性。

正如你所说,据我所知,“语法糖”。
注意,在foldLeft的情况下,例如,它们可能有点远 (与' /: '右联合运算符相当)


为了包括您的一些意见,稍加改动:

如果你考虑一个“追加”函数,左联合,那么你会写'一个oneTwo append 3 append 4 append 5 '。
但是,如果将3,4和5追加到两个(您将按照其写入的方式假设),则将是O(N)。
如果是“append”,则与“::”相同。 但事实并非如此。 实际上是“前置”

这意味着' a :: b :: Nil '是' List[].b.prepend(a) '

如果“::”要预先定好,但仍然是左联合的,那么结果列表的顺序是错误的。
你会期望它返回List(1,2,3,4,5),但是最终会返回List(5,4,3,1,2),这对程序员来说可能是意想不到的。
那是因为,你所做的将会是左联合的顺序:

 (1,2).prepend(3).prepend(4).prepend(5) : (5,4,3,1,2) 

因此,右关联性使得代码与返回值的实际顺序相匹配。

当您执行列表折叠操作时,右关联性和左关联性扮演着重要的angular色。 例如:

 def concat[T](xs: List[T], ys: List[T]): List[T] = (xs foldRight ys)(_::_) 

这工作得很好。 但是使用foldLeft操作不能执行相同的操作

 def concat[T](xs: List[T], ys: List[T]): List[T] = (xs foldLeft ys)(_::_) 

,因为::是正确关联的。

能够定义右联合方法的要点是什么?

我认为右联合方法的重点在于给人一个扩展语言的机会,这是操作符覆盖的一般点。

运算符重载是一件有用的事情,所以Scala说:为什么不把它打开到符号的任何组合呢? 相反,为什么区分运营商和方法呢? 现在,库实现者如何与Int等内置types进行交互? 在C ++中,她将在全局范围内使用friendfunction。 如果我们想要所有types实现operator ::

右结合提供了一个干净的方式来将::运算符添加到所有types。 当然,在技术上::运算符是Listtypes的一种方法。 但是它也是内置Int和所有其他types的虚构运算符,至less如果你可以忽略最后的:: Nil

我认为这反映了Scala在图书馆实施尽可能多的东西,并使语言灵活以支持他们的哲学。 这使得有人有机会提出SuperList,这可以被称为:

 1 :: 2 :: SuperNil 

有些不幸的是,正确的结合性目前只对结肠进行了硬编码,但我想这很容易记住。

Interesting Posts