Sometimes, the elegant implementation is just a function.
Not a method. Not a class. Not a framework. Just a function.
Not a method. Not a class. Not a framework. Just a function.
--John Carmac on Twitter
Dear reader, if you reading this post then you probably already have good justification why it worth to learn functional programming, so I don’t want to spent your time by enumerating here another dozen of arguments! Instead, my aim is to help you to adopt functional paradigm for “every day” programming. I mean for implementing user stories which most of us do on daily basis.
Also, I don't want to overload you with complex domain logic, but at the same time I’ll try to provide full of value and complete example of application with low-level coding details, so you could grasp “big picture” and see how the functional programming could be used for writing code which solves business problem.
Challenging with problem.
Our task would be to implement parking application, let’s call it “GarageGuru” with the following requirements:
- The planned garage should consist of individual parking lots. Each parking lot has unique location and could accommodate specified type(s) of vehicle. For ex: only Motorbike, or only Car or both types.
- Every parking lot could be in two states: “free” and “taken”.
- When user ask for parking then the application should look for available “free” parking lot, change its status to “taken” and return lot location where vehicle will be parked.
- When user wants take away his vehicle then the application should find “taken” lot where the vehicle was parked and clean it by changing its status back to “free”.
- Every vehicle is distinguished by an unique identifier (for ex. licence plate).
- The application should allow to find location of parked vehicle by its unique identifier.
Why Scala language?
The functional paradigm itself is not a trivial thing to learn, so it’s very useful to domesticate it step-by-step, gradually applying new concepts as you go on top of well known OOP style. Since, Scala is hybrid language and it allows to balance paradigm style in a range from totally OOP-like up to totally FP-like, I would recommend to begin from Scala, especially for those who came from Java world.
The Algebra of API design.
In object-oriented development we begin our API design with some interface which publishes the ultimate contract of the model to its client then we fill in this interface with concrete implementation object and classes. In functional programming we starting from defining Algebra of API, in other words we need to discover a data type and a set of primitive functions for our domain, and then derive some useful combinators. Let’s begin from modeling states machine: “free lot”->“taken lot”. In functional word the basic building blocks are immutable data types and function, so our first primitive functions for modeling state transitions are:
The Vehicle, FreeLot and TakenLot are some abstract data types, (so far we don’t care about concrete implementations) together with functions they form Algebraic API or Domain Algebra. Why it’s called Algebra? To cut a long story short, in math we call the following expressions algebraic:
def takeParkingLot( freeLot: FreeLot, vehicle: Vehicle ) : TakenLot
def cleanParkingLot( takenLot: TakenLot ) : FreeLot
The Vehicle, FreeLot and TakenLot are some abstract data types, (so far we don’t care about concrete implementations) together with functions they form Algebraic API or Domain Algebra. Why it’s called Algebra? To cut a long story short, in math we call the following expressions algebraic:
2x + x = 3x;
by analogy we might also call algebraic the expression like:
Vehicle + FreeLot = TakenLot
or in more domain friendly way:
Vehicle take FreeLot = TakenLot
For those of my readers who looking for more details about the functional way of handling state transitions, I would recommend to watch the following Mario Fusco's talk:
In addition to already defined functions, we also need the one which allows us to find free lot in a garage (repository):
def findFreeLot(vehicle: Vehicle) : FreeLot
But what if free parking is not found or some error happens while querying an underlying implementation of repository (for ex. database). Should we return null or throw an exception? Actually, by doing so we would violate fundamental principle of FP called “referential transparency” in other words our functions aren’t “pure” or “side effect free”. From FP point of view, the consequences of such violation are fatal - the function is no longer composable. Of course, there are tons alternatives, for example:
def findFreeLot(vehicle: Vehicle) : Try[FreeLot]
where Try[FreeLot] is a container type which carries FreeLot or Throwable.
Let’s add one more primitive function for searching parked vehicle and pack all of our Domain Algebra in a Trait. In scala the Trait is some sort of interface “on steroids”, it may contains variables, partially implemented methods, and lot of other useful stuff:
trait ParkingService[FreeLot, TakenLot, Vehicle, VehicleId] { def findFreeLot(vehicle: Vehicle) : Try[FreeLot] def findParkedVehicle(vehicleId: VehicleId) : Try[TakenLot] def takeParkingLot(freeLot: FreeLot, vehicle: Vehicle): Try[TakenLot] def cleanParkingLot(takenLot: TakenLot): Try[FreeLot] }
In the defined trait the part: [FreeLot, TakenLot, Vehicle, VehicleId] is definition of parametric types, which is equivalent of Generics in Java.
Composing functions into larger workflows.
Now, let’s consider use case n. 3 from the requirements list above, here we need to perform two steps: search for free lot and then take it, if such lot was found. For implementing given workflow it may be useful to reuse already defined functions: findFreeLot and takeParkingLot, we just need to chain them together so output of findFreeLot become input for takeParkingLot. Actually, one of the most prominent idea of functional programming is chaining and composition simpler functions into larger ones. Thus, the solution for our use case is completely supported by Scala syntax and looks like:
def parkVehicle(vehicle: Vehicle) : Try[TakenLot] = { for{ freeLot <- findFreeLot(vehicle) takenLot <- takeParkingLot(freeLot, vehicle) }yield (takenLot) }
We’ve used “for comprehensions” for Monads where Try[_] is a Modad and this means that it allows sequencing of functions. If you are new in functional programming and Monad is alien concept for you, then you could start from watching talk by Scott Wlaschin Railway oriented programming
Yet another awesome resource which tremendously helped me in learning FP is the book functional programming in scala.
Yet another awesome resource which tremendously helped me in learning FP is the book functional programming in scala.
The final Domain Algebra.
By analogy with parkVehicle(..), we also could compose functions for implementing takeAwayVehicle(..) workflow (mentioned in use case 4 from requirements list), so now the complete Domain Algebra looks like:
trait ParkingService[FreeLot, TakenLot, Vehicle, VehicleId] { def findFreeLot(vehicle: Vehicle) : Try[FreeLot] def findParkedVehicle(vehicleId: VehicleId) : Try[TakenLot] def takeParkingLot(freeLot: FreeLot, vehicle: Vehicle): Try[TakenLot] def cleanParkingLot(takenLot: TakenLot): Try[FreeLot] def parkVehicle(vehicle: Vehicle) : Try[TakenLot] = { for{ freeLot <- findFreeLot(vehicle) takenLot <- takeParkingLot(freeLot, vehicle) }yield (takenLot) } def takeAwayVehicle(vehicleId: VehicleId) : Try[FreeLot] = { for{ takenLot <- findParkedVehicle(vehicleId) freeLot <- cleanParkingLot(takenLot) }yield (freeLot) } }
This what beauty of functional design is about! We have composed larger workflows from simpler ones, we have no idea of what our types FreeLot, TakenLot, Vehicle will look like or how we will implement the behaviors takeParkingLot(..) or cleanParkingLot(..). But we already have complete implementation of parkVehicle(..) and takeAwayVehicle(..) which are formed out of simpler functions.
The interpreter for the domain algebra.
The actual implementation of the defined API (which in functional programming called interpreter) is located in my github: https://github.com/ddd-fun, repository "garage-guru-fun", branch "di-impl":
https://github.com/ddd-fun/garage-guru-fun/tree/di-impl
Please, feel free to pull or browse it for getting more details. In addition to FP application, I also implemented fully OOP-based equivalent in Java "garage-guru", so you could benefit from comparing both solutions.
No comments:
Post a Comment