Skip to content

Rewrote sequence comprehensions section of tour #754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 26, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 24 additions & 43 deletions tutorials/tour/_posts/2017-02-13-sequence-comprehensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,58 +13,39 @@ previous-page: extractor-objects

Scala offers a lightweight notation for expressing *sequence comprehensions*. Comprehensions have the form `for (enumerators) yield e`, where `enumerators` refers to a semicolon-separated list of enumerators. An *enumerator* is either a generator which introduces new variables, or it is a filter. A comprehension evaluates the body `e` for each binding generated by the enumerators and returns a sequence of these values.

Here is an example:
Here's an example:

```tut
object ComprehensionTest1 extends App {
def even(from: Int, to: Int): List[Int] =
for (i <- List.range(from, to) if i % 2 == 0) yield i
Console.println(even(0, 20))
}
```

The for-expression in function introduces a new variable `i` of type `Int` which is subsequently bound to all values of the list `List(from, from + 1, ..., to - 1)`. The guard `if i % 2 == 0` filters out all odd numbers so that the body (which only consists of the expression i) is only evaluated for even numbers. Consequently, the whole for-expression returns a list of even numbers.
case class User(val name: String, val age: Int)

The program yields the following output:
val userBase = List(new User("Travis", 28),
new User("Kelly", 33),
new User("Jennifer", 44),
new User("Dennis", 23))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although the style of indentation in parens varies widely, I'm not sure this one is a good one for a tutorial since we don't do trailing braces in Scala, generally, and trailing parens aren't that different from trailing braces. I'd format it as

val userBase = List(
  new User("Travis", 28),
  ...
  new User("Dennis", 23)
)


val twentySomethings = for (user <- userBase if (user.age >=20 && user.age < 30))
yield user.name // i.e. add this to a list

twentySomethings.foreach(name => println(name)) // prints Travis Dennis
```
List(0, 2, 4, 6, 8, 10, 12, 14, 16, 18)
```
The `for` loop used with a `yield` statement actually creates a `List`. Because we said `yield user.name`, it's a `List[String]`. `user <- userBase` is our iterator and `if (user.age >=20 && user.age < 30)` is a guard that filters out users who are in their 20s.

Here is a more complicated example using two generators. It computes all pairs of numbers between `0` and `n-1` whose sum is equal to a given value `v`:

Here is a more complicated example which computes all pairs of numbers between `0` and `n-1` whose sum is equal to a given value `v`:

```tut
object ComprehensionTest2 extends App {
def foo(n: Int, v: Int) =
for (i <- 0 until n;
j <- i until n if i + j == v) yield
(i, j);
foo(20, 32) foreach {
case (i, j) =>
println(s"($i, $j)")
}
def foo(n: Int, v: Int) =
for (i <- 0 until n;
j <- i until n if i + j == v)
yield (i, j)
Copy link

@Ichoran Ichoran May 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I'm not sure this is the best style to promote as it invites confusion about the comprehension part and the yield part. You don't indent the else farther than the corresponding if, so I'd tend to

for (...)
yield (i, j)

or

for (
  ...
)
yield (i, j)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it looks nicer but all of the examples in Odersky's book use an indent as does IntelliJ.


foo(10, 10) foreach {
case (i, j) =>
print(s"($i, $j) ") // prints (1, 9) (2, 8) (3, 7) (4, 6) (5, 5)
}
```

This example shows that comprehensions are not restricted to lists. The previous program uses iterators instead. Every datatype that supports the operations `withFilter`, `map`, and `flatMap` (with the proper types) can be used in sequence comprehensions.

Here's the output of the program:

```
(13, 19)
(14, 18)
(15, 17)
(16, 16)
Here `n == 10` and `v == 10`. On the first iteration, `i == 0` and `j == 0` so `i + j != v` and therefore nothing is yielded. `j` gets incremented 9 more times before `i` gets incremented to `1`. Without the `if` guard, this would simply print the following:
```

There is also a special form of sequence comprehension which returns `Unit`. Here the bindings that are created from the list of generators and filters are used to perform side-effects. The programmer has to omit the keyword `yield` to make use of such a sequence comprehension.
Here's a program which is equivalent to the previous one but uses the special for comprehension returning `Unit`:

(0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (0, 5) (0, 6) (0, 7) (0, 8) (0, 9) (1, 1) ...
```
object ComprehensionTest3 extends App {
for (i <- Iterator.range(0, 20);
j <- Iterator.range(i, 20) if i + j == 32)
println(s"($i, $j)")
}
```