Is this a pure function?
const add = (num1, num2) => num1 + num2How 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)
5Thus ✅. 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()
9Same input - not same output.
Thus ❌. Not a pure function
Given the String "hello" log will always print "hello" to the console.
> log("hello")
helloSame 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")
helloIs 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 " ++ namesayHello 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 ReaderLet’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 ReaderSo 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 :)