Подтвердить что ты не робот

Что такое принятый/рекомендуемый синтаксис для кода Scala с большим количеством методов-цепочек?

В 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), которое мне вообще нравится, предполагает, что ведущая точка-точка в цепочках вызовов может быть плохим (см. doc: Метод Invocation/Функции более высокого порядка), хотя он не распространяется на многострочные выражения, подобные этому напрямую.

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

ОБНОВЛЕНИЕ: 28 июня 2011 г.

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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

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

Аутентичный источник компилятора!

Ответ 4

Я предпочитаю много 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 вместо других упомянутых решений:

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

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

Ответ 5

Мое правило: если выражение подходит для одной строки (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

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

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

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

Ответ 6

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

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

Превосходная точечная нотация очень читаема. Я мог бы начать использовать это.