Что такое принятый/рекомендуемый синтаксис для кода 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% "вы должны сделать это таким образом", поэтому я собираюсь принять самый популярный ответ по голосам, который в настоящее время используется для понимания. Лично я думаю, что сейчас буду придерживаться нотации с ведущими точками и принять риски, которые приходят с ней.
Ответы
Ответ 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
Превосходная точечная нотация очень читаема. Я мог бы начать использовать это.