What happened to me when I started working with Scala can be summarized like this:
- I initially started using Scala as a “better Java”; I wrote OOP code with Scala
- As time went on I read Programming in Scala, and learned a little about the idea of a fusion of OOP and FP
- I started mixing pure functions into my OOP code
- I saw that pure functions were great because (a) output depended only on input and (b) there were no side effects
- I saw that classes like
Eitherwere great alternatives to throwing exceptions; I no longer had to think about my code short-circuiting when something bad happened
- As I’ll discuss shortly, because I knew exactly what was going into a function and coming out of a function, I began to trust type signatures again; I didn’t have to read every function’s code to see what it really did
- I realize people were on to something, FP probably wasn’t a fad, and I tried to learn more about it
All of this has to do with types, which I’ll get to in the following discussion.
Pure functions are great because you can trust them.
Writing pure functions means:
- A function’s output depends only on its input parameters
- The function does not modify its input parameters
- The function does not modify other “hidden” variables in its class
- The function does not communicate with the outside world: its has no I/O, no internet access, no database access
- Given the same input
X, the function always returns the same output
Examples of pure functions include:
- Getting the length or checksum of a string
- Math functions like +, -, *, and /
- More math functions like sin, cos
A thing to note about pure functions is that they always take input parameters and they never return
def stringLength(s: String): Int
def cos(x: Double): Double
def sum(a: Int, b: Int): Int
def sum(nums: Seq[Long]): Long
Because pure function signatures always include the types of the input parameters, a cool thing is that you can often guess what a function does just by looking at its type signature, even if you make the function name meaningless. For example, as a pop quiz, what do you think this function does:
def XYZ(s: String): Int = ???
Because the function takes a
String input parameter and returns an
Int — and also because you can be 100% sure that its a pure function with no side effects — the function can only do a few things:
- Calculate the length of the string
- Calculate some sort of checksum of the string
I initially wrote that the function could attempt to convert the string to an integer, but that function would look like this:
def XYZ(s: String): Option[Int] = ???
So that’s about it. Even without knowing the name of the function you can get a good idea from its type signature what it does. That’s cool.
In fact, that’s very cool. This is one of the great things about functional programming. Because functions are pure, you can just glance at the type signature of a function and have a great feel for what it does.
Not to gush too much, but I love writing pure functions: it makes things much easier on my brain. I don’t have to think about the entire application, I just have to think about what’s coming in and what’s going out.
And I like reading the signatures of pure functions that other people write. Unlike OOP style methods that can return
void and/or take no input parameters — which makes their type signatures meaningless — when I look at the type signature of a pure function, I can tell at a glance what’s going on.
Another pop quiz
If you liked that previous pop quiz, here’s another one. Knowing that this function is pure, what can it possible do:
def func(list: Seq[Int]): Int
Next, here’s the same function signature, but with a generic type. What can it possibly do:
def func(list: Seq[A]): A
I won’t give any answers for this one, but the important lesson is that you can look at the type signature of a pure function and generate some pretty good guesses about what that function does.