home search about rss feed twitter ko-fi

What To Think When You See That Opening Curly Brace (Scala 3 Video)

During the process of writing this book I had to get away from Scala for a while, and when I came back to it one of the first things I saw was some code that looked like this:

val x = FOO {
    // more code here
}

More accurately, the code I saw looked like this:

val x = FOO { (s: State) =>
    // more code here
}

Right away I had to ask myself, “Okay, Al, what in the world is FOO, and how does this code work?” It turns out that depending on exactly what the code looks like, FOO can be one of several things.

Goal

This style of code is used a lot by experienced Scala developers, so if you don’t know what FOO is, it’s another potential stumbling block to learning Scala/FP. Therefore, this lesson is about what FOO can possibly be when you see code that looks like that.

The short answer

The short answer is that FOO in this example:

val x = FOO {
    // more code here
}

can be several things:

  • An anonymous class
  • A function that takes a by-name parameter

If the code is slightly different and looks like this:

val f = FOO { (a: String) =>
    // more code here
}

it can be:

  • A class that takes a function parameter
  • A function that takes a by-name parameter

I’ll show examples of each of these in this lesson.

1) An anonymous class

In this code:

val mary = new Person {
    val name = "mary"
    val age = 22
}

I create an instance of Person using Scala’s “anonymous class” syntax. There’s no way for you to know it by looking at this code, but I could have defined Person as either a trait, abstract class (or less likely as a class), but for this example I created it as a trait:

trait Person {
    def name: String
    def age: Int
    override def toString = s"name: $name, age: $age"
}

When you paste that trait into the Scala REPL, and then also paste in this code:

val mary = new Person {
    val name = "mary"
    val age = 22
}
println(mary)

you’ll see this output:

name: mary, age: 22

Discussion

One thing you know about Person is that it’s not a case class. case classes don’t allow the use of the new keyword, and they must also have constructor parameters. Both of these things tell you that Person isn’t a case class.

It’s also unlikely, though possible, that Person is defined as a class. If Person is a class with name and age fields, those fields would require an override qualifier when mary is created. The only way Person can be a class in this example is if name and age are not defined in the class.

2) A function that takes a by-name parameter

A second thing that can look like this FOO example:

val x = FOO {
    // more code here
}

is a function that takes a by-name parameter.

In the Scala Cookbook I shared a timer function that can be used like this:

val (result, time) = timer {
    // some long-running block of code here
    // ...
}

Here’s a simple example of a block of code you can use with timer:

val (result, time) = timer {
    Thread.sleep(1000)
    42
}

When I run that code I get a result like this:

result: 42, time: 1004.819575

As that example shows, the timer function does three things:

  • Accepts a block of code
  • Runs the block of code
  • Returns the result of that code along with the length of time it took to run

In the Cookbook I showed that the timer code is defined to accept a by-name parameter named blockOfCode:

def timer[A](blockOfCode: => A) = {
    val startTime = System.nanoTime
    val result = blockOfCode
    val stopTime = System.nanoTime
    val delta = stopTime - startTime
    (result, delta/1000000d)
}

For the purposes of this lesson, this timer code:

val (result, time) = timer {
    Thread.sleep(1000)
    42
}

shows that a function that takes a by-name parameter is a second way to let people write code that looks like this pattern:

val x = FOO {
    // more code here
}

3) A class that takes a function parameter

Another code pattern that you’ll see that looks similar to the first two examples is this:

val f = FOO { (a: String) =>
    // more code here
}

In this code, FOO is either:

  • a case class that takes a function input parameter (FIP)
  • a function that takes a FIP

Furthermore, in this example that FIP must be defined to take one input parameter, which is either a String or a generic type.

As an example, you can create a case class that meets that criteria like this:

case class StringToInt(run: String => Int)

In this code, run is declared to be a FIP that transforms a String to an Int.

You can create an instance of that class by giving it an anonymous function, like this:

val stringToInt = StringToInt { s: String =>
    // algorithm can be as long as needed ...
    s.length
}

Now that you’ve created the stringToInt variable, you can call the run function on it at any later time:

// prints "7"
println(stringToInt.run("bananas"))

Note 1: Blurring out some code

One key to understand what’s happening with those curly braces is to recognize that the code shown in this image:

(The image is shown in the video.)

Blurring out code to understand what’s happening in the curly braces

is a function literal that takes a single input parameter (a String), and returns an Int (because the last line of the code block evaluates to an Int).

I find that “blurring out” code like that in my mind is a helpful technique to see the function literal when I see curly braces used like this.

Note 2: run is just a field in the class

Another important point to understand is that because run is a constructor input parameter:

case class StringToInt(run: String => Int)

it also becomes a public field in the StringToInt class. It’s a field in that class just like name is a field in this Person class:

case class Person(name: String)

Because name and run are both fields of their classes, this code:

val p = Person("mary")
println(p.name)

is similar to this code:

val stringToInt = StringToInt { s: String =>
    s.length
}
println(stringToInt.run("bananas"))

Note 3: Passing in a real function

If what’s happening in the StringToInt code isn’t clear, you may find that it’s easier to pass in an instance of a named function as opposed to using an anonymous function. To demonstrate this, here’s the StringToInt class again:

case class StringToInt(run: String => Int)

Next, rather than giving it an anonymous function, define a “regular” named function that matches run’s signature, i.e., a function that transforms a String to an Int:

def len(s: String) = s.length

Now you can create a new instance of StringToInt by passing len into StringToInt’s constructor:

val stringToInt = StringToInt(len)

This is the same approach as before, except this time I declared len as a “regular”, named function. Now I can write the last two lines of code as before:

// prints "5"
println(stringToInt.run("scala"))

Note 4: A more complicated example

Here’s a slightly more complicated example of this technique. The following code shows a class that is defined to take a function (a FIP) that has two input parameters of generic type A, and transforms those parameters into a potentially different type B:

case class Transform2ParamsTo1Param[A, B](fun: (A, A) => B)

To be clear, here’s the FIP signature by itself:

fun: (A, A) => B

Now I can write code like this to create an instance of Transform2ParamsTo1Param:

val x = Transform2ParamsTo1Param { (a: String, b: String) =>
    a.length + b.length
}

Then I can call fun on x like this:

// prints "6"
println(x.fun("foo", "bar"))

Because Transform2ParamsTo1Param defines its function input parameter fun to take generic types, I can also write code like this to take two Int values and return an Int:

val y = Transform2ParamsTo1Param { (a: Int, b: Int) =>
    a + b
}

// prints "3"
println(y.fun(1, 2))

While this might seem a little unusual if you come from an OOP background, this is just another example of a class that takes a function input parameter, i.e., an example of passing functions around.

If code like this isn’t comfortable right now, fear not, it wasn’t comfortable to me initially either. In my experience, I never got comfortable with it until I started using the technique in my own code. (Pro tip: Write a lot of code. As the saying goes, “One learns by doing the thing.”)

4) A function that takes a function input parameter

Getting back to my FOO examples ... another code pattern that can look like this:

val f = FOO { (a: String) =>
    // more code here
}

is when a function takes a function input parameter. I’ll show a variation of that in this section.

The following code looks like the FOO and Transform2ParamsTo1Param examples:

val res = s2i("hello") { s: String =>
    s.length
}

However, in this case s2i is a case class or a function that has two parameter groups. This code tells you that s2i’s first parameter group must take a String parameter (hello in this case), and the second parameter group takes a function that transforms a String to an Int (or it may use generic types).

While s2i can be implemented as either a function or as a case class, it’s most likely a function. I show both approaches next.

a) Implemented as a function

The function approach will have two parameter groups that look like this:

def s2i (s: String)(f: String => Int) = f(s)

You can verify this by pasting the following code into the Scala REPL:

def s2i (s: String)(f: String => Int) = f(s)

val res = s2i("hello") { s: String =>
    s.length
}

println(res)

That last line will print the number 5. This is a straightforward implementation of s2i.

b) Implemented as a case class

While this solution is a little more convoluted, it’s possible that s2i can be implemented as a case class, like this:

case class s2i (s: String)(_fun: String => Int) {
    def fun = _fun(s)
}

val res = s2i("hello") { s: String =>
    s.length
}

println(res.fun)

As the code shows, the primary difference to the consumer of s2i is that for the class you must call println(res.fun) to see the result, as opposed to calling println(res) with the function.

Aside: A practical use of multiple parameter groups

In the Scala Cookbook I shared a practical example of the multiple parameter group approach with this using code:

using(io.Source.fromFile("example.txt")) { source =>
    for (line <- source.getLines) {
        println(line)
    }
}

using is cool because it automatically calls the close method on the resource that’s passed into it in the first parameter group. As you’ll see in the code that follows, it calls close after the function that’s passed into the second parameter group is run. This is a smart and useful application of this technique.

In this case, using is defined as a function, like this:

def using[A <: { def close(): Unit }, B](resource: A)(f: A => B): B = {
    try {
        f(resource)
    } finally {
        resource.close()
    }
}

I first learned about the using control structure in the book, Beginning Scala. See the Scala Cookbook for a more thorough discussion of this code. Somewhere in history, someone named this code a “Loan Pattern,” and Joshua Suereth implemented the same technique in his Scala ARM Library.

5) A non-FP possibility: Reassignable Properties

There’s another technique that enables code like this, but a) the technique is rarely used, and b) it would never be used in functional programming. Therefore, I won’t write about it here, but if you’re interested in a technique that lets you write Scala/OOP code like this:

def top = new MainFrame {
    title = "First Swing App"
    contents = new Button {
        text = "Click me"
    }
}

see my article, Reassignable variables and properties.

Keys to remember

The following code examples provide a summary of the key points of this lesson.

Anonymous class

This code shows the creation of a Person instance using the “anonymous class” technique:

val mary = new Person {
    val name = "mary"
    val age = 22
}

In this specific example, Person may be defined as a trait or abstract class, or (much less likely) as a class.

A function that has a by-name parameter

In this example, timer is implemented as a function that declares a by-name parameter, and therefore takes a block of code:

val (result, time) = timer {
    // some long-running block of code here
    // ...
}

I implemented timer like this:

def timer[A](blockOfCode: => A) = {
    val startTime = System.nanoTime
    val result = blockOfCode
    val stopTime = System.nanoTime
    val delta = stopTime - startTime
    (result, delta/1000000d)
}

A case class that takes a function parameter

In this code, StringToInt is implemented as a case class that has a function input parameter (FIP), and that FIP apparently transforms a String to an Int:

val stringToInt = StringToInt { s: String =>
    s.length
}

For that code I defined this case class:

case class StringToInt(run: String => Int)

(It’s possible that the FIP also uses generic types.)

A case class that takes a FIP that has multiple input parameters

In this example:

val x = Transform2ParamsTo1Param { (a: String, b: String) =>
    a.length + b.length
}

Transform2ParamsTo1Param is defined as a case class that has a FIP, and that FIP must take two input parameters itself:

case class Transform2ParamsTo1Param[A, B](fun: (A, A) => B)
                                          ----------------

A function or class that has multiple parameter groups

I also showed that when you see code like this:

val res = s2i("hello") { s: String =>
    s.length
}

s2i will be a function or case class that has two parameter groups. It’s much more likely to be a function, which will be implemented like this:

def s2i (s: String)(f: String => Int) = f(s)

It’s possible that it could also be a case class, which will be written like this:

case class s2i (s: String)(_fun: String => Int) {
    def fun = _fun(s)
}

Pattern recognition

I wrote this lesson because over the last few years I’ve begun to look at computer programming as a form of pattern recognition. This was first made clear to me when I was reading a book about Lisp titled, The Little Schemer, where the authors state:

“The goal of this book is to teach the reader to think recursively ... It is our belief that writing programs recursively in Scheme is essentially simple pattern recognition.”

Some patterns are easy for most programmers to understand, and they’re adopted in many programming languages. Other patterns are easy for some programmers to recognize, but not for others. As I noted in this lesson, I stumbled on this pattern when I came back to Scala after a brief absence:

val x = FOO {
    // more code here
}

As a result, I tried to understand all of the things that FOO could possibly be, and I shared that information here.

A final reason I mention this specific pattern is because Scala/FP developers seem to use it very often. Because you’ll see it a lot in the code that follows, I wanted to be sure I covered it here.

Update: All of my new videos are now on
LearnScala.dev