On Purity

Three examples.

Is this a pure function?

const add = (num1, num2) => num1 + num2

How about this?

const getCurrentMonthIndex = () => new Date().getMonth()

Or this?

const log = (msg) => console.log(msg)

Well, what is a pure function?


A function is pure when given the same input, we always get the same output

Example 1

Given 2 and 3 add will always yield 5

> add(2,3)

Thus ✅. A pure function

Example 2

Given no input getCurrentMonthIndex will return us 9 as long as we are in October. As soon as we enter November it will return the number 10.

> getCurrentMonthIndex()

Same input - not same output.

Thus ❌. Not a pure function

Example 3

Given the String "hello" log will always print "hello" to the console.

> log("hello")

Same input, same output. A pure function?

What if?

> const console = "broken"
> log("hello")
Uncaught TypeError: console.log is not a function
    at log (REPL1:1:30)

By redefining console we changed the behaviour of our log function. Same input. Different output. As was expected.

❌ Not a pure function

So - How did we find out if these functions are pure?

Pretty much by reading the their implementation details.

Their function body.

And, of course, by testing (calling, invoking) them.

Super easy to do that with one-liners. Hard with functions spanning tens, hundreds or thousands of lines in length.

Creating purity

Can we rewrite both getCurrentMonthIndex and log to be pure?

Of course. We only need to declare their hidden inputs.

For the getCurrentMonthIndex it is the new Date() and for log the console.

> const getCurrentMonthIndex = (today) => today.getMonth()
> getCurrentMonthIndex(new Date())
> const log = (logger, msg) => logger.log(msg)
> log(console, "hello")

Is this helpful code? Is this good code?

Up for the reader to decide.

Have we made the hidden inputs visible?

No, there is is still the this present in all JavaScript functions. It is hidden but its there.

Are the functions pure?

Depends. Whether we consider them are pure or not - we probably can agree that they are more pure than they were before.

How did we find out if these functions are pure?

We did so by reading their code. Their implementation.

There is no other way of knowing!

Even if we call them with multiple inputs and check the output it is hard to cover the full space of available inputs.

Especially in the JavaScript case above where the input could be any type. We can add the Strings '4' + '2' and Numbers 4 + 2.

Is there an alternative where we don’t have to read the code?

Let’s play with the programming language Haskell.

sayHello :: String -> String
sayHello name = "Hello " ++ name

sayHello takes a String as an argument and returns a String

ghci> sayHello "Reader"
"Hello Reader"

An equivalent to console.log() would be putStrLn

ghci> putStrLn "Hello Reader"
Hello Reader

Let’s use it inside of sayHello

sayHello :: String -> String
sayHello name = putStrLn("Hello " ++ name)

Running this will give us an error.

test.hs:2:17: error:
    • Couldn't match type: IO ()
                     with: [Char]
      Expected: String
        Actual: IO ()
    • In the expression: putStrLn ("Hello " ++ name)
      In an equation for ‘sayHello’:
          sayHello name = putStrLn ("Hello " ++ name)
2 | sayHello name = putStrLn("Hello " ++ name)
  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^

String is expected and actual is IO (). The compiler does not allow us to do that.

We have to define the side-effect (putStrLn) in the function definition.

sayHello :: String -> IO ()
sayHello name = putStrLn("Hello " ++ name)
ghci> sayHello "Reader"
Hello Reader

So what?

We don’t have to read the implementation to learn whether the function is pure or not. Only the type signature!

A function taking String as input and returning a String as output is pure. It has to be.

Haskell is considered a purely functional programming language. Not because it has pure functions but because expressions in Haskell can be expressed exclusively in terms of a lambda calculus.

Lambda calculus?

Well, maybe something for our next post :)