Я думаю, что принятый ответ - это здорово, но, похоже, многим не удалось понять некоторые фундаментальные моменты.
Во-первых, for
понимание Scala эквивалентно записи do
в Haskell, и это не более чем синтаксический сахар для составления нескольких монадических операций. Поскольку это утверждение, скорее всего, не поможет никому, кто нуждается в помощи, давайте попробуем еще раз ...: -)
Scala's for
- синтаксический сахар для составления множества операций с картой, flatMap
и filter
. Или foreach
. Scala фактически переводит for
-выражение в вызовы этих методов, поэтому любой класс, предоставляющий их, или их подмножество, может использоваться для понимания.
Сначала поговорим о переводах. Есть очень простые правила:
Это
for(x <- c1; y <- c2; z <-c3) {...}
переводится на
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
Это
for(x <- c1; y <- c2; z <- c3) yield {...}
переводится на
c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
Это
for(x <- c; if cond) yield {...}
переведено на Scala 2.7 в
c.filter(x => cond).map(x => {...})
или, на Scala 2.8, в
c.withFilter(x => cond).map(x => {...})
с отступлением в первом случае, если метод withFilter
недоступен, но filter
есть. Пожалуйста, смотрите раздел ниже для получения дополнительной информации по этому вопросу.
Это
for(x <- c; y = ...) yield {...}
переводится на
c.map(x => (x, ...)).map((x,y) => {...})
Когда вы смотрите на очень простые for
понимания, альтернативы map
/ foreach
выглядят действительно лучше. Однако, как только вы начнете их составлять, вы можете легко потеряться в скобках и уровнях вложенности. Когда это происходит, for
понимания обычно намного яснее.
Я покажу один простой пример и намеренно опущу любое объяснение. Вы можете решить, какой синтаксис было легче понять.
l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))
или
for {
sl <- l
el <- sl
if el > 0
} yield el.toString.length
withFilter
В Scala 2.8 появился метод под названием withFilter
, основное отличие которого заключается в том, что вместо возврата новой отфильтрованной коллекции он фильтрует по требованию. Метод filter
определяет свое поведение на основе строгости коллекции. Чтобы лучше это понять, давайте взглянем на Scala 2.7 с List
(строгим) и Stream
(не строгим):
scala> var found = false
found: Boolean = false
scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9
scala> found = false
found: Boolean = false
scala> Stream.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
Разница возникает из-за того, что filter
немедленно применяется с List
, возвращая список шансов - так как found
равно false
. Только тогда выполняется foreach
, но к этому времени изменение found
не имеет смысла, так как filter
уже выполнено.
В случае Stream
условие не применяется немедленно. Вместо этого, поскольку каждый элемент запрашивается foreach
, filter
проверяет условие, что позволяет foreach
воздействовать на него через found
. Просто чтобы прояснить, вот эквивалентный код для понимания:
for (x <- List.range(1, 10); if x % 2 == 1 && !found)
if (x == 5) found = true else println(x)
for (x <- Stream.range(1, 10); if x % 2 == 1 && !found)
if (x == 5) found = true else println(x)
Это вызвало много проблем, потому что люди ожидали, что if
будет считаться по требованию, а не применяться ко всей коллекции заранее.
В Scala 2.8 введено withFilter
, что всегда не строгое, независимо от строгости коллекции. В следующем примере показано List
с обоими методами в Scala 2.8:
scala> var found = false
found: Boolean = false
scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9
scala> found = false
found: Boolean = false
scala> List.range(1,10).withFilter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
Это дает результат, которого ожидает большинство людей, без изменения поведения filter
. В качестве дополнительного примечания, Range
был изменен с нестрогого на строгий между Scala 2.7 и Scala 2.8.