Pure Functions

JIT: Pure Functions

The next JIT nugget of background information we need to cover is the definition of a pure function.

A pure function is a function:

  • Whose only parameters from the outside world are the input parameters that are defined in its function signature.
    • (I refer to the list of input parameters as a function’s front door.)
  • That does not modify those input parameters.
  • That does not modify other “hidden” variables that might be accessed through side doors, such as mutable variables in its class or any other global variables.
  • That does not communicate with the outside world. It has no I/O whatsoever: no file access, no internet access, no database access, no quantum entanglement, nothing.
  • That always returns the same output when its given the same input.
    • For example, sum(2, 2) is always 4.
  • That returns output for all possible input values.
  • That does not throw exceptions.

Because of those last two attributes, FPers say that a function is total, meaning that it always returns a value.

Examples of pure functions

Examples of pure functions include:

  • Getting the length or checksum of a string
  • Calculating the sum of a list of integers (List[Int])
  • Many mathematical functions

One thing to notice about pure functions is that they always have input parameters, and they never return Unit:

def stringLength(s: String): Int
                 ---------   ---
                   Input     Return
                 Parameters  Type

// more pure function signatures:
def sum(xs: List[Int]): Int
def add(a: Int, b: Int): Int
def cos(x: Double): Double

A pure function’s signature tells you what it does

Because pure function signatures in Scala always include the data types of (a) the input parameters, and (b) the function result, you can often guess what a function does just by looking at its type signature, even if the function name is meaningless. For example, what do you think this function does:

def f(s: String): Int = ???

Because the function takes a String input parameter and returns an Int — and also because you can be 100% sure that it’s a pure function — the function can only do a few things:

  • Calculate the length of the string
  • Calculate some sort of checksum or hash of the string

Even without knowing the name of the function, you can get a good idea from its type signature what it can possibly do. That’s cool.

In fact, that’s very cool.

This is one of the great things about pure functions: when functions are pure, you can just glance at their type signatures, and have a great feel for what they do.

In my case, once I saw this, I started to wish that all functions could somehow be pure.

It cannot be “String to Int”

Going back to that example, as I sketched its function signature, I thought for a moment that another thing the function could do is convert the String to an Int ... but that’s wrong. Because the process of attempting to convert a String to an Int can potentially fail, a pure function that does that MUST have one of these signatures in Scala:

def f(s: String): Option[Int] = ???
def f(s: String): Try[Int] = ???
def f(s: String): Either[Throwable, Int] = ???

As you’ll see in the following chapters, because attempting to convert a String to an Int can result in an error — such as trying to convert the string "Hi mom" — we need to return an error-handling type like Option, Try, or Either when writing this as a pure function. These types tell readers of our pure function, “something can go wrong here.” (I explain this in much more in the following chapters.)