Skip to content

Instantly share code, notes, and snippets.

@non
Last active Aug 29, 2015
Embed
What would you like to do?

Lambda, the Syntax

Functions are a core concept in functional programming (hence the name). In Scala, there are many ways to define functions (and function-like things). This short essay is aimed at trying to help make the distinctions (and syntax) a bit clearer.

Scala has two distinct function-like constructions:

  • Methods: These are traditional JVM methods (i.e. Java methods on some other class).
  • Functions: These are values (i.e. Java objects) which represent functions.

Both Methods and Functions often behave similarly (and we sometimes use the term function broadly to refer to both). However, in a narrow sense Functions are distinct from Methods. The following sections explore this in a bit more detail.

Methods

The simplest way to define a function in Scala is as a method:

def triple(x: Int): Int = x * 3

You can define a method like this in the REPL directly, but in a source file methods must be defined in class, trait, object, or another method. For this function (whose behavior is defined solely by x), we might say:

object Functions {
  def triple(x: Int): Int = x * 3
}

We would say Functions.triple(17) to call the method on 17.

You can think of methods as a generalization of Java's methods: methods on an object are sort of like static methods, methods on a trait are a bit like methods on an abstract class or interface, and so on. However, in Scala methods can be defined inside other methods:

def tripleTriple(x: Int): Int = {
  def innerTriple(y: Int): Int = y * 3
  innerTriple(innerTriple(x))
}

In this (somewhat contrived) case, we can use an inner method to avoid code duplication, or separate logic in a useful way. innerTriple may refer to values in tripleTriple's scope (although in this case it does not).

Functions

In some ways methods are the most natural way to define a function, but we can't treat a method as a value (at least not directly), which is something we often want to do in functional programming. There is no type signature that allows us to define a List of methods, for example.

Instead, Scala provides the Function1[-A, +B] type, which defines a function that takes a single A value and returns a single B value. (You can ignore what the - and + mean for now.) There are additional types like Function2[-A, -B, +C] which take two arguments (an A value and a B value) and returns a C value. Function types are defined for 0 through 22 (a Function0[+A] is a function that takes no arguments before returning an A value).

If we wanted to define a function that tripled an integer in this way we would say that it was of type Function1[Int, Int] and define it as follows:

val triple: Function[Int, Int] =
  new Function1[Int, Int] {
    def apply(x: Int): Int = x * 3
  }

(Similar to methods, we would usually define this val within a class, trait, object, or method.)

This is probably the most verbose way to define this function, but also the most explicit.

Function1[-A, +B] is abstract and requires an apply method to be defined, which takes an A value and returns a B value (in our case both A and B are Int). You can call a function just like you would a method:

triple(123)

Once you get used to defining function values, there are many terser ways we can define this function (from most verbose to least):

// This looks very similar to how you would define something
// that behaves like a function in Java 7 or earlier.
val triple: Function1[Int, Int] =
  new Function1[Int, Int] {
    def apply(x: Int): Int = x * 3
  }

// The => syntax implies that we are creating a function value
// and the fact that only one parameter ("x") precedes it indicates
// that it is a Function1.
val triple: Function1[Int, Int] =
  (x: Int) => x * 3

// Since we annotated "triple" with the type Function1[Int, Int]
//  we don't need to annotate "x" with an "Int" type.
val triple: Function1[Int, Int] =
  x => x * 3

// If the type of the input parameter is known (which it is in
// this case) and the parameter is only used once, we can use
// a single "_" to indicate how the parameter is used.
val triple: Function1[Int, Int] =
  _ * 3
  
// Finally, we can abbreviate the type Function1[Int, Int] as
// Int => Int. Both types are the same, and this usage is different
// to the x => x * 3 usage. => can be used both in type declarations
// but also as syntax for function values
val triple: Int => Int
  x => x * 3 // we could also have used _ * 3 here

When defining these function values it is important that Scala be able to determine what the input and output types are. In most cases, if Scala knows the input types, the output type can be determined (but there are instances where the compiler will need to be told).

Another case is creating a function value solely to pass as an argument to another function (for example map). In these cases we would say that map is a higher-order function (because it takes a function value as an argument). Here is a simple class that comes with a map method, and an example of how to use our "triple function" with it:

/**
 * This class holds exactly four values, which are all of type A.
 */
case class Quad[A](first: A, second: A, third: A, fourth: A) {

  /**
   * Convert our Quad[A] to a Quad[B] by calling `f` on each
   * value in the current quad.
   */
  def map[B](f: A => B): Quad[B] =
    Quad(f(first), f(second), f(third), f(fourth))
}

// first let's define a starting value.
val quad: Quad[Int] = Quad(1, 1, 3, 4)

// next, let's use map to triple each of the quad's fields.
val tripledQuad: Quad[Int] = quad.map(x => x * 3)

// we could also have said quad.map(_ * 3)
// or quad.map((x: Int) => x * 3)
// or even quad.map(new Function1 { def apply(x: Int): Int = x * 3 })

The map method on Quad doesn't just allow us to triple Int values, we can also define funtions which return a different type. Here's how we can turn our quad value into a Quad[String] (a quad of strings instead of integers):

val stringQuad: Quad[String] = quad.map(x => x.toString)
// or possibly just quad.map(_.toString)

In this case, we don't even need to annotate stringQuad's type. Scala can figure out that x => x.toString is a Function1[Int, String] value, which means that from map's point-of-view, B is String. This means that map will be returning a Quad[String] value, which is exactly what happens.

Methods as functions

Earlier I said that methods can't be treated as functions. But that was not totally correct -- there is a syntax to turn methods into functions.

Here's an example which converts the double method into a Function1[Float, Float] value called doubler:

object Stuff {
  def double(x: Float): Float = x * 2

  val doubler: Float => Float =
    double _
}

Scala has a deserved reputation for overusing underscores. In this case, the underscore is doing something totally different: it is turning the method into a function value!

Using our previous syntax for functions, we can see double _ as a shorthand for (x: Float) => double(x).

In some cases, you can even omit the _ and it will be implied. Here's an example using Quad[Float]:

val q: Quad[Float] = Quad(1.2F, 0F, 33F, 14.7F)

val q2: Quad[Float] = q.map(Functions.double)
// this is the same as q.map(Functions.double _)
// and also as q.map(x => Functions.double(x))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment