简单来说,从右往左结合可以通过使程序员输入与程序实际执行一致来提高可读性。
因此,如果您键入'
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)
这个方法既更易读,又更高效(对于prepend
来说是O(1),而假设有一个append
方法则为O(n))。
(提醒,摘自书籍Programming in Scala)
如果一个方法使用操作符表示法,例如a * b
,则该方法在左操作数上被调用,就像a.*(b)
一样——除非方法名以冒号结尾。
如果方法名以冒号结尾,则该方法在右操作数上被调用。
因此,在1 :: twoThree
中,::
方法在twoThree
上被调用,并传入1,就像这样:twoThree.::(1)
。
对于List,它扮演附加操作的角色(该列表似乎附加在“1”之后形成'1 2 3
',实际上是将1插入到列表中)。
类List不提供真正的附加操作,因为附加到列表需要的时间随着列表大小的增长而线性增长,而使用::进行前置操作只需恒定的时间。
myList :: 1
会尝试将myList的整个内容前置到“1”上,这比将1前置到myList中要长(如'1 :: myList
')
注意:无论运算符具有什么结合性,它的操作数始终从左到右进行评估。
因此,如果b是一个不仅仅是对不可变值的简单引用的表达式,则a ::: b更精确地被视为以下块:
{ val x = a; b.:::(x) }
在这个块中,a仍然在b之前被评估,然后将该评估的结果作为操作数传递给b的 ::: 方法。
为什么要区分左结合和右结合方法呢?这样可以保持通常的左结合操作外观("
1 :: myList
"),同时实际上在右表达式上应用操作,因为:
- 这更有效率。
- 但是使用逆结合顺序("
1 :: myList
" vs. "
myList.prepend(1)
")更易读。
所以正如你所说,“语法糖”,据我所知。
请注意,在
foldLeft
的情况下,例如,他们可能
有点过头了(等价于'
/:
'的右结合运算符)。
为了包含一些你的评论,稍作改动:
如果你认为'append'函数是左结合的,那么你会写'
oneTwo append 3 append 4 append 5
'。
然而,如果它要将3、4和5附加到oneTwo(根据写法),那么复杂度将是O(N)。
'::'也是如此,只不过是用于“prepend”。
这意味着'
a :: b :: Nil
'是为了'
List[].b.prepend(a)
'。
如果'::'是要进行prepend并且仍保持左结合性,则结果列表将会顺序颠倒。
你希望它返回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)
因此,右结合使代码与返回值的实际顺序匹配。