Issue #22132 has been reported by shugo (Shugo Maeda). ---------------------------------------- Feature #22132: Scala-like for comprehensions https://bugs.ruby-lang.org/issues/22132 * Author: shugo (Shugo Maeda) * Status: Open ---------------------------------------- ## Abstract How about adding an expression form of `for` that desugars into nested `flat_map`/`map` and `filter` calls. Here's an example, which computes all pairs of numbers between `0` and `n-1` whose sum is equal to a given value `v`: ```ruby def foo(n, v) for i in 0...n, j in 0...n when i + j == v then [i, j] end end p foo(10, 10) #=> [[1, 9], [2, 8], [3, 7]...] ``` The above code is desugared as follows: ```ruby def foo(n, v) (0...n).flat_map { |i| (0...n).filter { |j| i + j == v }.map { |j| [i, j] } } end p foo(10, 10) ``` ## Background and Motivation Some other languages have syntactic sugar that flattens nested code. For example, Scala has for comprehensions: ```scala def foo(n: Int, v: Int) = for i <- 0 until n j <- 0 until n if i + j == v yield (i, j) ``` Haskell has do notation: ```haskell foo :: Int -> Int -> [(Int, Int)] foo n v = do i <- [0 .. n-1] j <- [0 .. n-1] guard (i + j == v) pure (i, j) ``` Blocks are often nested deeply in Ruby, so such syntactic sugar is useful. ## Use cases For comprehensions can be used to flatten nested blocks. For example, ```ruby (1..).lazy.flat_map { |z| (1..z).lazy.flat_map { |x| (x..z).lazy.filter { |y| x**2 + y**2 == z**2 }.map { |y| [x, y, z] } } }.take(3).force ``` can be flattened as follows: ```ruby for z in (1..).lazy, x in (1..z).lazy, y in (x..z).lazy when x**2 + y**2 == z**2 then [x, y, z] end.take(3).force ``` For comprehensions can be used not only for Enumerable objects, but also for other objects which have `flat_map` and `map` that satisfy [the Monad laws](https://wiki.haskell.org/index.php?title=Monad_laws). ## Why `then` and `when`? Scala's `yield` conflicts with the existing `yield` keyword in Ruby, so I chose `then`. While a bare `for ... then` (no guard) reads a little unnaturally in English, Ruby already gives `then` a value-producing meaning (e.g., `Kernel#then`), so I consider it acceptable. The guard keyword is not `if` but `when`, because `if` after the source would be ambiguous with the modifier `if`. ## Limitations The right operand of `in` is `arg_value`, not `expr_value`, to avoid conflicts, so unparenthesized method calls (command calls) must be parenthesized. ## Backward compatibility * `for x in xs do ... end` (and the newline form) is unchanged: it still iterates via `each` and returns the collection. * All of the new forms (`for ... then`, `for ... ,`, `for ... when`) were `SyntaxError` before, so no existing program changes meaning. ## Implementation PoC: https://github.com/ruby/ruby/pull/17500 It's currently implemented only in `parse.y`, not in Prism yet, so requires `--parser=parse.y`. ## Open questions * Variable scope: the loop variables currently leak, same as `for ... do`. Should a comprehension instead scope them like a block? -- https://bugs.ruby-lang.org/