May 8, 2019

FP for OO - Part 3

Functional Exceptionalism

Previous Post: https://blog.marktranter.com/the-m-word/

In Part 1, we looked at Pure Functions and saw an obvious problem when trying to do anything practical. Namely, how do we do exceptions when we can't throw exceptions?

Thats the topic of today's episode.

But first, lets back track have a deeper look at the Option type in Scala for a second.

sealed trait Option[T]
case class Some[T](t: T) extends Option[T]
case object None extends Option[Nothing]

For a look at Nothing, see here: https://medium.com/@juntomioka/what-is-scalas-nothing-type-for-6d1a1d4bcc02

And our API's may look something like this:

def getUser(id: String): Option[User]

What the Option does here is to lift the concept of null from the world of values into the world of types.

Once we have encoded the concept of null into the type system like this, we have exposed it to the type checker. So we can't pass Option[Int] to a function expecting Int. This seems pretty basic.

This gives us compile time guarantees against Null References. Shielding us from the Billion Dollar Mistake

So can we do the same with exceptions?

The Either[T, A] Type
sealed trait Either[EX, A]
case class Right[A](a: A) extends Either[Nothing, A]
case class Left[EX](e: EX) extends Either[EX, Nothing]

The Either type encodes the concept of failure into the type system in the same way that Option encodes the concept of null.

By convention, the Right side of an Either represents success. So the Left represents a failure. It is the logical disjunction, or Coproduct of two types.

Lets look at an example.

trait AccountsAPI {
   def getUser(id: String): Either[Exception, User]
   def getAccount(user: User): Either[Exception, Account]
   def getTransactions(a: Account): Either[Exception, List[Account]]
}

object Program {
    private val api: AccountsAPI = ???
    
    def getUserTx(userId: String): Either[Exception, List[Account]]  = {
       api.getUser(userId)
           .flatMap(api.getAccount)
           .flatMap(api.getTransactions)
    }
}

By now, this flatMap syntax should be pretty familiar. We use it here to compose functions that return values whose computation may fail.

We can bail out early if a function in our chain returns a Left (exceptional) value. So if getUser returns a Left(SomeException()) then getUserTx also returns a Left(SomeException()). This is analogous to empty Lists or Options.

So here we have lifted Exceptions from the world of values, into the world of types. We've exposed them to the type checker. One less thing to hold in our mental stack. More cognitive room for meaningful functionality.

Contents

Intro - https://blog.marktranter.com/fp-introduction/
What is FP - https://blog.marktranter.com/what-is-functional-programming/
The M Word - https://blog.marktranter.com/fp-in-oo/
Functional Exceptionalism - https://blog.marktranter.com/functional-exceptionalism/