Scala 中的右结合方法有什么好处? [英] What good are right-associative methods in Scala?

查看:22
本文介绍了Scala 中的右结合方法有什么好处?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚开始玩 Scala,我刚刚了解了如何使方法右结合(而不是更传统的左结合在命令式面向对象语言中很常见).

I've just started playing around with Scala, and I just learned about how methods can be made right-associative (as opposed to the more traditional left-associativity common in imperative object-oriented languages).

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

At first, when I saw example code to cons a list in Scala, I had noticed that every example always had the List on the right-hand side:

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

然而,即使一遍遍地看,我也没有三思而后行,因为我(当时)不知道 ::列表.我只是假设它是一个运算符(再次,在 Java 中的运算符的意义上)并且关联性并不重要.List 总是出现在示例代码的右侧这一事实似乎是巧合(我认为这可能只是首选样式").

However, even after seeing this over and over again, I didn't think twice about it, because I didn't know (at the time) that :: is a method on List. I just assumed it was an operator (again, in the sense of operator in Java) and that associativity didn't matter. The fact that the List always appeared on the right-hand side in example code just seemed coincidental (I thought it was maybe just the "preferred style").

现在我更清楚了:它必须这样写,因为 :: 是右结合的.

Now I know better: it has to be written that way because :: is right-associative.

我的问题是,能够定义右结合方法有什么意义?

纯粹是出于审美原因,还是在某些情况下右结合实际上比左结合有某种好处?

Is it purely for aesthetic reasons, or can right-associativity actually have some kind of benefit over left-associativity in certain situations?

从我(新手)的角度来看,我真的不明白

From my (novice) point-of-view, I don't really see how

1 :: myList

myList :: 1

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

but that's obviously such a trivial example that I doubt it's a fair comparison.

推荐答案

简而言之,右结合可以通过使程序员的类型与程序的实际操作保持一致来提高可读性.
因此,如果您键入1 :: 2 :: 3",您将返回一个 List(1, 2, 3) ,而不是以完全不同的顺序返回一个 List.
那是因为 '1 :: 2 :: 3 :: Nil' 实际上是

The short answer is that right-associativity can improve readability by making what the programmer type consistent with what the program actually does.
So, if you type '1 :: 2 :: 3', you get a List(1, 2, 3) back, instead of getting a List back in a completely different order.
That would be because '1 :: 2 :: 3 :: Nil' is actually

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))
  • more readable
  • more efficient (O(1) for prepend, vs. O(n) for an hypothetic append method)

(提醒,摘自在 Scala 中编程)
如果在运算符符号中使用方法,例如 a * b,则在左操作数上调用该方法,如 a.*(b) — 除非该方法名称以冒号结尾.
如果方法名称以冒号结尾,则在右操作数上调用该方法.
因此,在1::twoThree中,::方法在twoThree上被调用,传入1,像这样:twoThree.::(1).

(Reminder, extract from the book Programming in Scala)
If a method is used in operator notation, such as a * b, the method is invoked on the left operand, as in a.*(b) — unless the method name ends in a colon.
If the method name ends in a colon, the method is invoked on the right operand.
Therefore, in 1 :: twoThree, the :: method is invoked on twoThree, passing in 1, like this: twoThree.::(1).

对于List,它起到了追加操作的作用(列表似乎是在'1'后面追加形成'1 2 3',实际上它是1,即预先到列表中).
类 List 不提供真正的追加操作,因为追加到列表所花费的时间随列表的大小线性增长,而以 :: 开头需要恒定时间.
myList :: 1 会尝试将 myList 的全部内容添加到 '1',这比在 myList 中添加 1 的时间要长(如 '1 :: myList')

For List, it plays the role of an append operation (the list seems to be appended after the '1' to form '1 2 3', where in fact it is 1 which is prepended to the list).
Class List does not offer a true append operation, because the time it takes to append to a list grows linearly with the size of the list, whereas prepending with :: takes constant time.
myList :: 1 would try to prepend the entire content of myList to '1', which would be longer than prepending 1 to myList (as in '1 :: myList')

注意:不管一个运算符有什么结合性,它的操作数都是总是从左到右计算.
因此,如果 b 是一个不仅仅是对不可变值的简单引用的表达式,那么 a ::: b 更准确地被视为以下块:

Note: No matter what associativity an operator has, however, its operands are always evaluated left to right.
So if b is an expression that is not just a simple reference to an immutable value, then a ::: b is more precisely treated as the following block:

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

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

In this block a is still evaluated before b, and then the result of this evaluation is passed as an operand to b’s ::: method.

为什么要区分左关联方法和右关联方法?

why make the distinction between left-associative and right-associative methods at all?

这允许保留通常的左关联操作('1 :: myList')的外观,同时实际将操作应用于右侧表达式,因为;

That allows to keep the appearance of a usual left-associative operation ('1 :: myList') while actually applying the operation on the right expression because;

  • 效率更高.
  • 但它使用逆关联顺序更具可读性('1 :: myList' vs. 'myList.prepend(1)')
  • it is more efficient.
  • but it is more readable with an inverse associative order ('1 :: myList' vs. 'myList.prepend(1)')

正如你所说,语法糖",据我所知.
请注意,以 foldLeft 为例,他们可能有 走得有点远(与 '/:' 右结合运算符等效)

So as you say, "syntactic sugar", as far as I know.
Note, in the case of foldLeft, for instance, they might have gone a little to far (with the '/:' right-associative operator equivalent)

为了包括您的一些评论,稍微改写:

To include some of your comments, slightly rephrased:

如果你考虑一个左关联的追加"函数,那么你应该写oneTwo append 3 append 4 append 5".
但是,如果要将 3、4 和 5 附加到 oneTwo(您会根据它的编写方式假设),则为 O(N).
如果是用于追加",则与 '::' 相同.但事实并非如此.它实际上是为了前置"

if you consider an 'append' function, left-associative, then you would write 'oneTwo append 3 append 4 append 5'.
However, if it were to append 3, 4, and 5 to oneTwo (which you would assume by the way it's written), it would be O(N).
Same with '::' if it was for "append". But it is not. It is actually for "prepend"

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

That means 'a :: b :: Nil' is for 'List[].b.prepend(a)'

如果 '::' 是前置的并且仍然保持左关联,那么结果列表的顺序将是错误的.
您可能希望它返回 List(1, 2, 3, 4, 5),但它最终会返回 List(5, 4, 3, 1, 2),这可能是程序员意想不到的.
那是因为,您所做的应该是按左关联顺序:

If '::' were to prepend and yet remain left-associative, then the resulting list would be in the wrong order.
You would expect it to return List(1, 2, 3, 4, 5), but it would end up returning List(5, 4, 3, 1, 2), which might be unexpected to the programmer.
That is because, what you have done would have been, in the left-associative order:

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

因此,右结合使代码与返回值的实际顺序相匹配.

So, the right-associativity makes the code match up with the actual order of the return value.

这篇关于Scala 中的右结合方法有什么好处?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆