Каков принятый / рекомендуемый синтаксис для кода Scala с большим количеством цепочек методов? - PullRequest
22 голосов
/ 24 июня 2011

В 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

Руководство по стилю Scala Даниэля Спивака (pdf), которое мне обычно нравится, предполагает, что запись лидирующей точки в цепочечных вызовах методов может быть неправильной (см. Документ: Вызов метода / Функции высшего порядка), хотя он не распространяется на многострочные выражения, подобные этому напрямую.

Есть ли другой, более приемлемый / идиоматический способ написания функции foo выше?

ОБНОВЛЕНИЕ: 28 июня 2011 года

Множество отличных ответов и обсуждений ниже. Кажется, нет 100% ответа «вы должны сделать это таким образом», поэтому я собираюсь принять самый популярный ответ голосованием, который в настоящее время является подходом для понимания. Лично я думаю, что я собираюсь придерживаться обозначения ведущей точки и принять риски, которые с этим связаны.

Ответы [ 6 ]

16 голосов
/ 24 июня 2011

Пример немного нереалистичен, но для сложных выражений часто гораздо понятнее использовать понимание:

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
11 голосов
/ 24 июня 2011

Я заключаю все выражение в набор скобок, чтобы сгруппировать объекты и избегать точек, если это возможно,

def foo: List[Int] =
  ( (1 to 100).view
    map { _ + 3 }
    filter { _ > 10 }
    flatMap { table.get }
    take(3)
    toList )
6 голосов
/ 27 июня 2011

Вот как это делает extempore.Вы не ошибетесь.

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

Источник аутентичного компилятора!

5 голосов
/ 25 июня 2011

Я предпочитаю много val s:

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 s.Большим преимуществом является выбор val s вместо других упомянутых решений:

Очевидно, что делает ваш код.В вашем примере и в решениях, упомянутых в большинстве других ответов, любой не на первый взгляд знает, что он делает.В одном выражении слишком много информации.Только в выражении for, упомянутом @Kevin, можно выбрать говорящие имена, но они мне не нравятся, потому что:

  1. Им нужно больше строк кода
  2. Онимедленнее из-за того, что шаблон соответствует заявленным значениям (я упомянул это здесь ).
  3. Просто мое мнение, но я думаю, что они выглядят некрасиво
2 голосов
/ 24 июня 2011

Мое правило: если выражение помещается на одной строке (80-120 символов), сохраняйте ее на одной строке и по возможности пропускайте точки:

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

Обозначение ведущей точки вполне приемлемо, если вам нужно разделить выражение на несколько строк по длине. Другая причина использования этой нотации - когда для операций нужны отдельные комментарии. Если вам нужно распределить выражение по нескольким строкам из-за его длины или необходимости комментировать отдельные операции, лучше всего обернуть все выражение в скобки ( как Алекс Бойсверт предлагает . В этих ситуациях каждая (логическая) операция должна идти в своей собственной строке (т. е. каждая операция идет в одной строке, за исключением случаев, когда несколько последовательных операций можно кратко описать одним комментарием) :

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

Этот метод позволяет избежать потенциальных проблем с выводом точки с запятой , которые могут возникнуть при использовании обозначения начальной точки или при вызове метода 0-arg в конце выражения.

1 голос
/ 24 июня 2011

Я обычно стараюсь избегать использования точек для таких вещей, как map и filter.Поэтому я, вероятно, написал бы это следующим образом:

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

Обозначение начальной точки очень читабельно.Я мог бы начать использовать это.

...