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
Given 2 and 3 add
will always yield 5
> add(2,3)
5
Thus ✅. A pure function
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()
9
Same input - not same output.
Thus ❌. Not a pure function
Given the String "hello"
log
will always print "hello"
to the console.
> log("hello")
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.
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())
9
> const log = (logger, msg) => logger.log(msg)
> log(console, "hello")
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 :)