IO for Great Good
!(Side Effects) #
Code is hard in large part because of side effects. If you call a function, it does stuff, and you might not always know what it does. Why? Because it interacts with the outside world.
The concept of a function interacting with the outside world is called purity, and there’s a fancy mathematical explanation of it. But I’m not here to spout a bunch of math at you. The 80% approximate explanation is this: if a function talks to anything outside of its body, it is impure.
So, being pure means you can’t do a lot of things. If your function is pure, it cannot:
- write or read any global variable
- talk to a database
- make a web request
- talk to a cache
- do anything immediately useful
If you missed it, look at #5 again. So if it’s useless, why the post about purity? Purity has one great advantage: referential transparency.
Again, fancy mathematical explanation aside, referential transparency means this: you can replace a function call in your program with a static value and the program will not change. That sounds weird I know, but the benefits to this principle in your everyday code are huge. Imagine if you had a pure function called myFunc, and you called it in your codebase. Replace every call to myFunc with a value. Your code gets easier to follow because you don’t have to remember what myFunc does.
Now extend that idea to all the pure functions in your program. The more you have, the easier the code gets. Now we’re getting somewhere.
Side Effects #
All this purity business is great. Our code is easier to follow and everything, but as we all know, programs that don’t interact with the world are pretty useless. So, how do we keep most of our code pure while still keeping things useful? In Scala (well, scalaz specifically), there is a class called IO
that you can use to solve this problem.
Some code:
import scalaz.effects._
def doThings: List[IO[String]] = {
val talkToDB = io {
val res = dbConn.executeQuery("SELECT * FROM 'some_table'")
res.toString
}
val talkToCache = io {
if(cache.exists("SOMEKEY")) "HIT" else "MISS"
}
List(talkToCache, talkToDB)
}