Mike Slinn

Lambda Review & Drill

— Draft —

Published 2015-02-05. Last modified 2015-09-13.
Time to read: 5 minutes.

Anonymous functions are also known as lambdas. It takes effort to become comfortable with using lambdas in their various forms and scenarios, but this skill is essential in order to work with Scala effectively. This lecture is intended to give you a summary of the material presented so far, followed by a drill.

As always, I recommend that you follow along.

Please try out every code example in the REPL.

Review

A lambda that accepts one parameter of type SomeType and returns an instance of AnotherType is an instance of a Function1, and is of the form:

How to define a lambda function
(x: SomeType) => x.methodInvocation

You can assign a lambda to a variable:

Assigning a lambda to a variable
val fn = (x: SomeType) => x.methodInvocation

Let’s work with an example where SomeType is String and AnotherType is Int.

Scala REPL
scala> val fn = (x: String) => x.length
fn: String => Int = <function1> 

We can invoke fn by passing it the value "hi there" for the parameter x like this.

Scala REPL
scala> fn("hi there")
res1: Int = 8 

Note that the type of fn is String => Int, which is shorthand for Function1[String, Int]. ScalaDoc uses this shorthand. The above has no declared type; the type is inferred by the Scala compiler.

Careful, do not forget to provide parentheses around the declaration of the x parameter:

Scala REPL
scala> val fn = x: String => x.length
<console>:7: error: not found: value x
val fn = x: String => x.length
^ 

We could explicitly declare the type of fn with this equivalent expression:

Scala REPL
scala> val fn: (String) => Int = (x: String) => x.length
fn: String => Int = <function1> 

The resulting Function1 definition is identical and of course the returned value is the same as before.

Scala REPL
scala> fn("hi there")
res1: Int = 8 

When there is only one argument to the function the parentheses around the parameter in the type declaration are optional. We can remove the parentheses around the parameter in the type declaration for a Function1, like this.

Scala REPL
scala> val fn: String => Int = (x: String) => x.length
fn: String => Int = <function1> 

The resulting Function1 definition is identical to the previous lambda definition, and of course the returned value from invoking the lambda is the same as before.

Scala REPL
scala> fn("hi there")
res1: Int = 8 

Here is the identical definition, but written in long form:

Scala REPL
scala> val fn: Function1[String, Int] = (x: String) => x.length
fn: String => Int = <function1> 

You can invoke this version to verify that it works just the same:

Scala REPL
scala> fn("hi there")
res2: Int = 8 

There is redundancy in the above definition for n because the type of the parameter (String) is mentioned twice. You could shorten the above because the type of fn is declared on the left hand side of the equals sign, which tells the Scala compiler that the type of the variable x is String, and that the return value is Int.

Scala REPL
scala> val fn: String => Int = x => x.length
fn: String => Int = <function1> 

Here is the identical definition, written in long form:

Scala REPL
scala> val fn: Function1[String, Int] = x => x.length
fn: String => Int = <function1> 

Notice that x is mentioned twice, but only used once. This is redundant. When you have a lambda that only references a parameter once you can use placeholder syntax to shorten the definition even further. In this case the underscore means "the variable passed in as an argument which has the type as specified elsewhere". In this case the type is specified on the left-hand side of the equals sign.

Scala REPL
scala> val fn: Function1[String, Int] = _.length
fn: String => Int = <function1> 

Of course you could also declare the type using shorthand for an identical result:

Scala REPL
scala> val fn: String => Int = _.length
fn: String => Int = <function1> 

You could also write this with the type information on the right-hand side of the equals sign using an alternative form of placeholder syntax for lambdas.

Scala REPL
scala> val fn = (_: String).length
fn: String => Int = <function1>
scala>
fn("hi there") res5: Int = 8

As you will learn in the Higher-Order Functions lecture of the Intermediate Scala course, when you have a higher-order function, the type of the function that must be passed into the higher-order function is declared as part of the method signature of the higher-order function. Thus the compiler knows the method signature required for Functions passed to the higher-order function as an argument.

Next we see a higher-order function that accepts two arguments: a String and a Function1[String, Int] called fn. We can pass in any of the definitions for fn above when we invoke the method.

Scala REPL
scala> def myHigherOrderFunction(string: String, fn: String => Int): Int = fn(string)
myHigherOrderFunction: (string: String, fn: String => Int)Int
scala>
myHigherOrderFunction("blah", fn) res4: Int = 4

Drills

Drill 1

Which of these define the same Function1?

  1. Scala code 1
    val fn1 = (x: Int) => x * 2
  2. Scala code 2
    val fn2: Int => Int = _ * 2
  3. Scala code 3
    val fn3: Function1[Int, Int] = x => x * 2
  4. Scala code 4
    val fn4 = (_:Int) * 2

Solution 1

All of them.

Scala REPL
scala> val fn1 = (x: Int) => x * 2
fn: Int => Int = <function1>
scala>
fn1(21) res0: Int = 42
scala>
val fn2: Int => Int = _ * 2 fn: Int => Int = <function1>
scala>
fn2(21) res1: Int = 42
scala>
val fn3: Function1[Int, Int] = x => x * 2 fn: Int => Int = <function1>
scala>
fn3(21) res2: Int = 42
scala>
val fn4 = (_:Int) * 2 fn: Int => Int = <function1>
scala>
fn4(21) res3: Int = 42

Drill 2

What is the equivalent long-form type declaration for this lambda?

(i: Int, str: String) => str * i
  1. Function1[Int, String, String]
  2. Function2[Int, String, String]
  3. Int => String
  4. (Int, String) => String

Solution 2

Let’s run the lambda to see what it does:

Scala REPL
scala> (i: Int, str: String) => str * i
res4: (Int, String) => String = <function2>
scala>
res4(3, "hi ") res5: String = "hi hi hi "

The lambda accepts an Int and a String, and returns a new String that repeats the original string i times.

#1 and #3 are clearly incorrect because a Function1 can only accept one parameter, and this lambda accepts two parameters.

#2 is correct. The type of the lambda, expressed in shorthand, is (Int, String) => String, and expressed in long form this is Function2[Int, String, String].

#4 is the lambda’s actual type as reported by the REPL, but this is expressed in shorthand, not long form as required by the question.

Drill 3

What is the equivalent long-form type declaration for this lambda?

Short form lambda type definition
(_: String) * (_: Int)
  1. Function1[String, Int, String]
  2. Function2[String, Int, String]
  3. String => Int
  4. (String, Int) => String

Solution 3

Let’s run the lambda to see what it does:

Scala REPL
scala> (_: String) * (_: Int)
res5: (Int, String) => String = <function2>
scala>
res5("hi ", 3) res5: String = "hi hi hi "

The lambda accepts two unnamed parameters, using placeholder syntax: a String and an Int. The lambda returns a new String that repeats the original string the number of times specified by the Int. The placeholder syntax can only be used when each parameter is only referenced once.

#1 and #3 are clearly incorrect because a Function1 can only accept one parameter, and this lambda accepts two parameters.

#2 is correct. The type of the lambda, expressed in shorthand, is (String, Int) => String. Expressed in long form this is Function2[String, Int, String].

#4 is the actual type of the lambda, expressed in shorthand, not long form as required by the question.

Again, note that the reversing the order of the parameters causes a syntax error.

Scala REPL
scala> (_: Int) * (_: String)
<console>:8: error: overloaded method value * with alternatives:
(x: Double)Double <and>
(x: Float)Float <and>
(x: Long)Long <and>
(x: Int)Int <and>
(x: Char)Int <and>
(x: Short)Int <and>
(x: Byte)Int
cannot be applied to (String)
(_: Int) * (_: String) 
^

This is because the * method is bound to the first parameter, as if you wrote:

Scala lambda type definition
(_: Int).*((_: String))

Int has no method called * that accepts a String, hence the compiler error.

Drill 4

Which of these define the same Function2?

  1. Scala code 1
    val fn1 = (str: String, i: Int) => str * i
  2. Scala code 2
    val fn2: (String, Int) => String = _ * _
  3. Scala code 3
    val fn3: Function2[String, Int, String] = (x, y) => x * y
  4. Scala code 4
    val fn4 = (_: String) * (_:Int)

Solution 4

All of them.

Scala REPL
scala> val fn1 = (str: String, i: Int) => str * i
fn1: (String, Int) => String = <function2>
scala>
fn1("hi ", 3) res11: String = "hi hi hi "
scala>
val fn2: (String, Int) => String = _ * _ fn2: (String, Int) => String = <function2>
scala>
fn2("hi ", 3) res12: String = "hi hi hi "
scala>
val fn3: Function2[String, Int, String] = (x, y) => x * y fn3: (String, Int) => String = <function2>
scala>
fn3("hi ", 3) res13: String = "hi hi hi "
scala>
val fn4 = (_: String) * (_:Int) fn4: (String, Int) => String = <function2>
scala>
fn4("hi ", 3) res14: String = "hi hi hi "

Drill 5

Which are shorthand equivalents for the following long form FunctionN type?

Scala code
Function3[String, Int, Int, Boolean]
  1. (String) => (Int) => (Int) => (Boolean)
  2. (String, Int, Int, Boolean)
  3. (String, Int, Int) => Boolean
  4. (String) => Boolean

Solution 5

#1 is an example of a curried function, which we will discuss in the Partially Applied Functions lecture of the Intermediate Scala course.

#2 does not define a FunctionN because no rocket operator (=>) was provided.

#3 is correct. Here is an example:

Scala REPL
scala> val fn: (String, Int, Int) => Boolean = (s, i, j) => (s*i).length == j
fn: (String, Int, Int) => Boolean = <function3>
scala>
fn("abc", 5, 15) res1: Boolean = true

#4 defines a Function1, not a Function3.

Exercise - Spark

The Spark documentation shows this method signature.

Spark method signature (Scala code)
def subgraph(
  epred: EdgeTriplet[VD,ED] => Boolean = (x => true),
  vpred: (VertexId, VD) => Boolean = ((v, d) => true)
): Graph[VD, ED]

The documentation actually has an error, in that VertexId was mis-spelled as VertexID. I’ve corrected that here.

  1. Explain the type of epred.
  2. Where does x come from?
  3. What does (x => true) do?

Solution

The subgraph method accepts two parameters: epred and vpred. epred is declared to have type EdgeTriplet[VD,ED] => Boolean, which means that it accepts something of type EdgeTriplet[VD,ED] and returns a Boolean. It also has a default value: x => true. There was no need to enclose the default value in parentheses, and I would discourage that.

x => true is a lambda function and must be of type EdgeTriplet[VD,ED] => Boolean. Thus x must be of type EdgeTriplet[VD,ED] and true is of course type Boolean.

The value of x will be obtained from epred when subgraph is invoked. I think the intent would have been clearer if an underscore was used instead of a named variable, since the value of x is discarded. Here is how I would have written the method signature:

Scala code
def subgraph1(
  epred: EdgeTriplet[VD,ED] => Boolean = _ => true,
  vpred: (VertexId, VD) => Boolean = (_, _) => true
): Graph[VD, ED] = ???

Here is another way to write the method signature, which is more explicit about types:

Scala code
def subgraph2(
  epred: EdgeTriplet[VD,ED] => Boolean = _ => true,
  vpred: (VertexId, VD) => Boolean = (_: (VertexId, VD)) => true
): Graph[VD, ED] = ???

You can play with the code:

Shell
$ git clone https://github.com/huangjs/spark-sample-project
$ cd spark-sample-project
$ sbt console

Paste this into the REPL:

Scala code
import org.apache.spark.graphx._
class X[VD, ED] { def subgraph1( epred: EdgeTriplet[VD,ED] => Boolean = _ => true, vpred: (VertexId, VD) => Boolean = (_, _) => true ): Graph[VD, ED] = ???
def subgraph2( epred: EdgeTriplet[VD,ED] => Boolean = _ => true, vpred: (VertexId, VD) => Boolean = (_: (VertexId, VD)) => true ): Graph[VD, ED] = ??? }

* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.