A block in Scala is just an expression. Like parentheses, they are useful for grouping code together. Unlike parentheses, blocks don't contain just one expression, but can contain 1 or more expressions. The value of a block is the value of the last expression in it.
{ println("Hi"); _ + 1 }
is equivalent to { println("Hi"); (i: Int) => i + 1 }
. This is a block that prints out "Hi"
, and it's value is a function that adds one. The string is printed before execution exits the block, and the function that it produces doesn't know anything about the println
.
Here are some examples of these rules:
list.map(i => i + 1)
// ^----------^
// function literal passed as argument
list.map(_ + 1)
// ^---^
// Underscore shorthand for above
list.map({ i => i + 1 })
// Identical to above.
// The block only contains one expression, so it has the value of that expression
// Otherwise stated: { expr } === expr
list.map({ println("Hi"); _ + 1 })
// ^-----2-----^ ^-3-^
// ^------------1---------^
// 1: The argument to map is the value of this block
// 2: The first statement of the block prints something. This is only executed once,
// because it's not the *block* being passed as argument, it's its value.
// 3: Function literal in underscore notation. This is the value of the block
// and this is what map sees.
// Order of operations (approx. bytecode):
// load list onto stack
// load string "Hi" onto stack
// call println and pop string off stack
// create function (i => i + 1) on top of stack
// invoke map with argument (i => i + 1), popping list and function off stack
list.map { println("Hi"); _ + 1 }
// Identical to above, but Scala lets you omit the () because you are using {}
list.map({ i => println("Hi"); i + 1 })
// Function literals grow as big as they can be.
// The block contains only one expression, which is (i => println("Hi"); i + 1)
// This function prints "Hi" and then returns i + 1
// This call to map will print "Hi" for every element
list.map { i => println("Hi"); i + 1 }
// Identical to above, but Scala lets you omit the () because you are using {}
Additionally, there are by-name parameters to deal with. By-name parameters are declared like so:
def func(a: => String) // Not () => String, it's => String
When you have by-name parameter, then
func { println("x"); _ + 1 }
actually passes the whole block as argument. The block still evaluates to i => i + 1
, but func
is in control of when that evaluation happens. Specifically, the code of the block is turned into a Function0
and passed into func
, which can call it as many times as it likes, with side effects. This can be used to great effect, essentially allowing normal functions to act like custom control flow operators:
@tailrec def repeat(i: Int)(op: => Any): Unit
= if(i == 0) ()
else {
require(i >= 0, s"negative repeat amount: $i")
op // Evaluate op
repeat(i - 1)(op) // Won't evaluate op; will let sub-call deal with it
}
repeat(5) { println("Hi"); println("Bye") }
// Hi
// Bye
// Hi
// Bye
// Hi
// Bye
// Hi
// Bye
// Hi
// Bye
Note how the parentheses are omitted around the block, which really makes this seem like the ability to define a control flow operator.