对于具有大量方法链接的 Scala 代码,可接受/推荐的语法是什么?

发布于 2024-11-17 05:01:21 字数 733 浏览 5 评论 0原文

在 Scala 中,我倾向于使用 val 赋值来编写大型链式表达式,而不是许多较小的表达式。在我的公司,我们已经为这种类型的代码发展了一种风格。这是一个完全人为的示例(想法是显示包含大量链式调用的表达式):

import scala.util.Random
val table = (1 to 10) map { (Random.nextInt(100), _) } toMap

def foo: List[Int] =
  (1 to 100)
    .view
    .map { _ + 3 }
    .filter { _ > 10 }
    .flatMap { table.get }
    .take(3)
    .toList

Daniel Spiewak 的 Scala 风格指南 (pdf),我通常喜欢,建议链式方法调用中的前导点表示法可能不好(请参阅文档:方法调用/高阶函数),尽管它不直接涵盖这样的多行表达式。

是否有另一种更可接受/惯用的方式来编写上面的函数 foo ?

更新:2011 年 6 月 28 日

下面有很多精彩的答案和讨论。似乎没有 100%“你必须这样做”的答案,所以我将接受投票中最受欢迎的答案,这是目前用于理解的方法。就我个人而言,我认为我现在会坚持使用前导点表示法,并接受随之而来的风险。

In Scala I tend to favour writing large chained expressions over many smaller expressions with val assignments. At my company we've sort of evolved a style for this type of code. Here's a totally contrived example (idea is to show an expression with lots of chained calls):

import scala.util.Random
val table = (1 to 10) map { (Random.nextInt(100), _) } toMap

def foo: List[Int] =
  (1 to 100)
    .view
    .map { _ + 3 }
    .filter { _ > 10 }
    .flatMap { table.get }
    .take(3)
    .toList

Daniel Spiewak's Scala Style Guide (pdf), which I generally like, suggests the leading dot notation in the chained method calls may be bad (see doc: Method Invocation / Higher-Order Functions), though it doesn't cover multi-line expressions like this directly.

Is there another, more accepted/idiomatic way to write the function foo above?

UPDATE: 28-Jun-2011

Lots of great answers and discussion below. There doesn't appear to be a 100% "you must do it this way" answer, so I'm going to accept the most popular answer by votes, which is currently the for comprehension approach. Personally, I think I'm going to stick with the leading-dot notation for now and accept the risks that come with it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

遗失的美好 2024-11-24 05:01:21

该示例有点不切实际,但对于复杂的表达式,使用推导式通常要清晰得多:

def foo = {
  val results = for {
    x <- (1 to 100).view
    y = x + 3 if y > 10
    z <- table get y
  } yield z
  (results take 3).toList
}

这里的另一个优点是您可以命名计算的中间阶段,并使其更加自记录。

如果简洁是你的目标,那么这可以很容易地变成一行(无点风格在这里有帮助):

def foo = (1 to 100).view.map{3+}.filter{10<}.flatMap{table.get}.take(3).toList
//or
def foo = ((1 to 100).view map {3+} filter {10<} flatMap {table.get} take 3).toList

并且一如既往,尽可能优化你的算法:

def foo = ((1 to 100).view map {3+} filter {10<} flatMap {table.get} take 3).toList
def foo = ((4 to 103).view filter {10<} flatMap {table.get} take 3).toList
def foo = ((11 to 103).view flatMap {table.get} take 3).toList

The example is slightly unrealistic, but for complex expressions, it's often far cleaner to use a comprehension:

def foo = {
  val results = for {
    x <- (1 to 100).view
    y = x + 3 if y > 10
    z <- table get y
  } yield z
  (results take 3).toList
}

The other advantage here is that you can name intermediate stages of the computation, and make it more self-documenting.

If brevity is your goal though, this can easily be made into a one-liner (the point-free style helps here):

def foo = (1 to 100).view.map{3+}.filter{10<}.flatMap{table.get}.take(3).toList
//or
def foo = ((1 to 100).view map {3+} filter {10<} flatMap {table.get} take 3).toList

and, as always, optimise your algorithm where possible:

def foo = ((1 to 100).view map {3+} filter {10<} flatMap {table.get} take 3).toList
def foo = ((4 to 103).view filter {10<} flatMap {table.get} take 3).toList
def foo = ((11 to 103).view flatMap {table.get} take 3).toList
凉栀 2024-11-24 05:01:21

我将整个表达式包装在一组括号中以对事物进行分组并尽可能避免使用点,

def foo: List[Int] =
  ( (1 to 100).view
    map { _ + 3 }
    filter { _ > 10 }
    flatMap { table.get }
    take(3)
    toList )

I wrap the entire expression into a set of parenthesis to group things and avoid dots if possible,

def foo: List[Int] =
  ( (1 to 100).view
    map { _ + 3 }
    filter { _ > 10 }
    flatMap { table.get }
    take(3)
    toList )
苏别ゝ 2024-11-24 05:01:21

这是即兴的做法。你不会出错的。

(specMember
  setInfo   subst(env, specMember.info.asSeenFrom(owner.thisType, sym.owner))
  setFlag   (SPECIALIZED)
  resetFlag (DEFERRED | CASEACCESSOR | ACCESSOR | LAZY)
)

正宗编译源码!

Here's how extempore does it. You can't go wrong.

(specMember
  setInfo   subst(env, specMember.info.asSeenFrom(owner.thisType, sym.owner))
  setFlag   (SPECIALIZED)
  resetFlag (DEFERRED | CASEACCESSOR | ACCESSOR | LAZY)
)

Authentic compiler source!

九命猫 2024-11-24 05:01:21

我更喜欢使用大量 val

def foo = {
  val range = (1 to 100).view
  val mappedRange = range map { _+3 }
  val importantValues = mappedRange filter { _ > 10 } flatMap { table.get }
  (importantValues take 3).toList
}

因为我不知道您想要使用代码实现什么目的,所以我为 val 选择了随机名称。
选择 val 而不是其他提到的解决方案有一个很大的优势:

你的代码的作用很明显。在您的示例和大多数其他答案中提到的解决方案中,任何人第一眼都不知道它的作用。一种表达方式包含了太多的信息。只有在 @Kevin 提到的 for 表达式中,才可以选择告诉名称,但我不喜欢它们,因为:

  1. 它们需要更多行代码
  2. 由于模式匹配声明的值,它们速度较慢(我提到过这个 此处)。
  3. 只是我的意见,但我觉得他们看起来很丑

I prefer lots of vals:

def foo = {
  val range = (1 to 100).view
  val mappedRange = range map { _+3 }
  val importantValues = mappedRange filter { _ > 10 } flatMap { table.get }
  (importantValues take 3).toList
}

Because I don't know what you want to purpose with your code, I chose random names for the vals.
There is a big advantage to choose vals instead of the other mentioned solutions:

It is obvious what your code does. In your example and in the solutions mentioned in most other answers anyone does not know at first sight what it does. There is too much information in one expression. Only in a for-expression, mentioned by @Kevin, it is possible to choose telling names but I don't like them because:

  1. They need more lines of code
  2. They are slower due to pattern match the declared values (I mentioned this here).
  3. Just my opinion, but I think they look ugly
太傻旳人生 2024-11-24 05:01:21

我的规则:如果表达式适合一行(80-120 个字符),请将其保留在一行上并尽可能省略点:

def foo: List[Int] = 
   (1 to 100).view map { _ + 3 } filter { _ > 10 } flatMap table.get take 3 toList

正如 Kevin 指出的,无点样式可能会提高简洁性(但可能会损害开发人员的可读性)不熟悉):

def foo: List[Int] = 
   (1 to 100).view map{3+} filter{10<} flatMap table.get take 3 toList

如果由于长度原因需要将表达式分隔成多行,则前导点表示法是完全可以接受的。使用此表示法的另一个原因是当操作需要单独的注释时。 如果您需要将表达式分布在多行中,由于其长度或需要注释单独的操作,最好将整个表达式括在括号中(如 亚历克斯·博伊斯维特建议在这些情况下,每个(逻辑)操作应该在自己的行上(即每个操作在一行上,除非可以描述多个连续操作)。简洁地通过一条评论):

def foo: List[Int] = 
   ( (1 to 100).view
     map { _ + 3 }
     filter { _ > 10 }
     flatMap table.get
     take 3
     toList )   

此技术避免了使用时可能出现的潜在分号推理问题前导点符号或在表达式末尾调用 0-arg 方法。

My rule: if the expression fits on a single (80-120 character) line, keep it on one line and omit the dots wherever possible:

def foo: List[Int] = 
   (1 to 100).view map { _ + 3 } filter { _ > 10 } flatMap table.get take 3 toList

As Kevin pointed out, the point-free style may improve brevity (but could harm readability for developers not familiar with it):

def foo: List[Int] = 
   (1 to 100).view map{3+} filter{10<} flatMap table.get take 3 toList

The leading dot notation is perfectly acceptable if you need to separate the expression over multiple lines due to length. Another reason to use this notation is when the operations need individual comments. If you need to spread an expression over multiple lines, due to its length or the need to comment individual operations, it's best to wrap the entire expression in parens (as Alex Boisvert suggests. In these situations, each (logical) operation should go on its own line (i.e. each operation goes on a single line, except where multiple consecutive operations can be described succinctly by a single comment):

def foo: List[Int] = 
   ( (1 to 100).view
     map { _ + 3 }
     filter { _ > 10 }
     flatMap table.get
     take 3
     toList )   

This technique avoids potential semicolon inference issues that can arise when using leading dot notation or calling a 0-arg method at the end of the expression.

梦晓ヶ微光ヅ倾城 2024-11-24 05:01:21

我通常会尽量避免在 mapfilter 等内容中使用点。所以我可能会像下面这样写:

def foo: List[Int] =
  (1 to 100).view map { x =>
    x + 3 } filter { x =>
    x > 10 } flatMap { table.get } take(3) toList

前导点符号非常可读。我可能会开始使用它。

I usually try to avoid using dot for things like map and filter. So I would probably write it like the following:

def foo: List[Int] =
  (1 to 100).view map { x =>
    x + 3 } filter { x =>
    x > 10 } flatMap { table.get } take(3) toList

The leading dot notation is very readable. I might start using that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文