我刚刚开始玩Scala,我刚刚学习了如何使方法成为右关联的(与命令式面向对象语言中常见的更传统的左关联性相反)。
最初,当我在Scala中看到示例代码cons
一个列表时,我注意到每个示例始终在右侧都有该List:
println(1 :: List(2, 3, 4))
newList = 42 :: originalList
但是,即使一遍又一遍地看到它,我也没有三思而后行,因为(当时)我不知道
::
是List
的一种方法。我只是假定它是一个运算符(再次,就Java中的运算符而言),并且关联性并不重要。 List
始终出现在示例代码的右侧这一事实似乎是偶然的(我认为这可能只是“首选样式”)。现在我知道了:它必须以这种方式编写,因为
::
是右关联的。我的问题是,能够定义右关联方法有什么意义?
是纯粹出于美学原因,还是在某些情况下右联想会比左联想有某种好处?
从我(新手)的 Angular 来看,我真的不明白
1 :: myList
有什么比
myList :: 1
但这显然是一个微不足道的例子,我怀疑这是一个公平的比较。
最佳答案
简短的答案是,通过使程序员类型与程序实际行为保持一致,右联想可以提高可读性。
因此,如果您键入“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)
。对于列表,它起到附加操作的作用(列表似乎附加在“1”之后,形成“
1 2 3
”,实际上是在列表前面的1)。类列表没有提供真正的追加操作,因为追加到列表所需的时间随列表的大小线性增长,而加上::则需要固定的时间。
myList :: 1
会尝试将myList的全部内容添加到'1'之前,这比将1添加到myList之前要长(如在1 :: myList
中)注意:无论运算符具有何种关联性,其操作数均为
总是从左到右评估。
因此,如果b是一个表达式,而不仅仅是对不可变值的简单引用,则::: b更精确地视为以下块:
{ val x = a; b.:::(x) }
在此块中,a仍在b之前进行评估,然后评估结果
作为操作数传递给b的:::方法。
why make the distinction between left-associative and right-associative methods at all?
在实际将操作应用于右表达式时,这样可以保持通常的左关联操作('
1 :: myList
')的外观,因为;1 :: myList
”与“myList.prepend(1)
”对比)更具可读性据您所知,“句法糖”据我所知。
注意,例如对于
foldLeft
,它们可能具有gone a little to far(与'/:
'右关联运算符等效)要包括您的一些评论,请稍作改写:
如果您考虑左关联的“附加”功能,则应编写“
oneTwo append 3 append 4 append 5
”。但是,如果将3、4和5附加到oneTwo(您将以其编写的方式假定),则它将为O(N)。
与“::”相同,如果用于“追加”。但事实并非如此。它实际上是为“前置”
这意味着“
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)
因此,右关联性使代码与返回值的实际顺序匹配。
关于scala - Scala中的右关联方法有什么用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1162924/