I am always confused about the use of () and {}

I am familiar with higher order functions including the ones in python, ruby and some of scala.
But I am confused about the use of () and {} for higher functions in scala.
Please look at this:

scala> val hash = Map("jeff" -> "hello", "joan" -> "worldtest", "nancy" -> "greetings baby")

scala> hash.map( x => x._2.size)
val res35: scala.collection.immutable.Iterable[Int] = List(5, 9, 14)

scala> hash.map{ x => x._2.size}
val res36: scala.collection.immutable.Iterable[Int] = List(5, 9, 14)

Both () and {} work above.
But in this case the () doesn’t work:

scala> hash.map( case (x,y) => y.size )
                 ^
       error: illegal start of simple expression

scala> hash.map{ case (x,y) => y.size }
val res37: scala.collection.immutable.Iterable[Int] = List(5, 9, 14)

So, is there a principle when to use () and when to use {} for the input parameters?

Thank you for the kind answers.

1 Like

Ruby always use {} as block parameter. Just for fun I expanded ruby’s Array to add some functions for map/reduce.

Perl always use sub {} as the block arguments.
Python use the keyword “lambda” for anonymous function.
So, maybe scala should make the rule more clear? We should use {} as the only enclose form of code block.

(sorry I am the newbie, if I have misunderstood it please forgive me)

thanks.

You are facing one of the many confusions the excessive amount of sugar syntax causes on newcomers (and even experienced folks). That is actually why I would rather remove most sugar from the language… but well, many people disagree with me on that.

Anyways, let’s start with the basics, you are not changing {} with () you are just omitting the former.
Remember that 3 + 4 expands to 3.+(5) so as you can see we can replace the . with a space and omit the parenthesis of the argument. Those are the two basic rules.

Then we have {} which essentially create a block expression, so you can add them almost anywhere without changing the behavior of your code like: { { { 3 } } } is equivalent to 3 just wrapped on a bunch of block expressions that eventually all evaluate to the same 3 value; remember the type of a block expression is the type of its last expression because that is what they return.

Now, if you want to have multiple lines you need blocks.
Thus:

foo.map(x => x + 1)
// Can also be written like:
foo.map({ x => x + 1})
foo.map { x => x + 1 }
foo map { x => x + 1 }
foo map { x =>
  x + 1
}

// But, if you have:
foo.map { x =>
  val y = bar(x)
  val z = y + 5
  x + y + z
}
// Then it is sugar for:
foo.map({ x =>
  val y = bar(x)
  val z = y + 5
  x + y + z
})

So as you can see the parenthesis are always there and the block can be optional sometimes. All this with the purpose that you can write the code you find more readable but at the consequence of being more complex.

Speaking of complex, I haven’t covered the hard parts.
As I said, {} creates a block, but { x => ??? } is not a valid block syntax AFAIK.
So I am not sure if there is even more sugar syntax over that and it should be x => { ??? } or if there is other kind of block specific for lambdas.
We also have the thing that case requires a block… which makes sense since it should be sugar for pattern matching… or maybe not? Apparently, that is an especial syntax for PartialFunction, except it is not, it can also create normal functions according to the expected type…

And… that was the moment when I decided it wasn’t worth it to try to understand all the sugar that was happening.
So no, don’t feel sad if you are confused by this, I sincerely believe there is no Scala programmer who knows all that is actually happening, maybe compiler folks like Som but yeah you get the idea.

4 Likes

Thank you for the detailed answer @BalmungSan
Now it seems to me like both the () and {} around the arguments can be optional sometime.
It could be more clear anyway if there is always the {} around the code block.

regards.

There are just a couple of simple rules.

A function that takes a single parameter f(x) can take a block expression f { x }.

The spec says just that:

If only a single argument is supplied, it may be supplied as a block expression and parentheses can be omitted, in the form f { block }. This is valid when f has a single formal parameter or when all other formal parameters have default values.

The second part is a bit obscure to me: You can use a block if you’re supplying a single arg.

An example where other args are implicit is Future { body }.

An example where other args are default args is a test API in scala.tools.testkit

assertThrows(body, checkMessage = checker)

which can be invoked with the default checkMessage:

assertThrows { ??? }

What is a block? It’s either some statements and a result expression, or a block of case clauses.

The spec links to the explanation of case clauses as a pattern-matching anonymous function.

List(42).collect { case i if i < 27 => i * 2 }

You get a function or a partial function, depending on the context.

For a regular block, such as

List(42).map { val x = 2 ; i => i * x }

the function literal is the result expression of the block. The code to the right of the arrow can be a sequence of block statements

List(42).map { val x = 2 ; i => val y = 3 ; i * x * y }

No extra braces are required around the body of the lambda (because the big block is already wrapped in braces, if you like).

It looks a little less naked when the block has only the result expression and begins with the formal parameter of the lambda:

List(42).map { i => val x = 2 ; val y = 3 ; i * x * y }

And it also looks more normal when written vertically.

Syntactically, it’s not weirder that the RHS of a lambda in a block result is a block, than that the RHS of a lamba expression is an expression (which may be a lambda):

(i: Int) => (j: Int) => i+j

It’s not terrible to write

List(42).map(i => { val x = 2 ; val y = 3 ; i * x * y })

as is necessary in a multiarg application

assertThrows(body, msg => { val n = msg.length ; n > 3 })

3 Likes

good answer. thanks @som-snytt

Sorry for your SIMS (syntax-induced metabolic syndrome) due to simple sugars.

I would agree that this example is hard to read

List(42).map { val x = 2 ; i => val y = 3 ; i * x * y }

but some boolean expressions are also obscurantist, or

List(42).filter { case X(n) => ??? case _ => ??? }

where an optional semicolon is omitted. (I omitted the optional hyphen in semi-colon.)