Why use Functional style in OO?
Functional Programming and OO are different implementations of programming. Why then would we want to combine ideas from opposing concepts? We as developers understand that we can attack a problem from many different angles; using appropriate functional concepts in OO is no different.
First off, I’m not suggesting that you treat Ruby exactly like you would a functional ecosystem. Ruby is inherently Object Oriented, so we should utilize the disciplines of the language. However, exploring functional programming through the Elixir programming language has given me new ways to reason about architecture and design even in OO systems.
Let’s get down to business!
What benefits can we achieve from using a functional style? Functional implementations can give us the ability to write side-effect-free code. What is side-effect-free code? Side effects in code are changes we can verify from the outside after calling a function or method. In order for a method to be side-effect-free it must return a value and do nothing else. An example would be
puts("ruby"). This takes an input and returns an output, we didn’t mutate anything in our system. This is how true functional languages behave, they don’t allow for mutation. Functions take in data and return a value without mutating the original data. Side effects are essentially what cause bugs. These bugs can grow in complexity when you don’t understand the current state of the world.
Another way to think about this concept is to think about it mathematically.
Mutating our value of
x produces a false answer in our equation.
Let’s look at another example. If you’re a Rails dev, you are used to seeing code like this in a controller.
Looking at this code we can see that our blog calls an update with the result of another method. This method,
blog_params is side-effect-free, it returns a value. How about our
.update? This code could be riddled full of side effects. The update method mutates the attributes of our blog instance. This mutation happens even if a validation fails. Do we care if these attributes are mutated? Well it depends on your understanding of the rest of the code surrounding it. What if we want to re-render with the previous values? What if there is a callback fired after we update/save? What affect will mutated attributes have? The point here is that this mutation of data can cause adverse side effects, so we should be aware of it. Now, I am not saying you shouldn’t have any side effects in your code either, apps simply wouldn’t run without side effects. Persisting data to a DB is a side effect in itself. We now know that side effects change the world of the app, and can cause hard to trace bugs. How then do we stop mucking up state? For the above example a pattern named the repository pattern can help us extract a functional layer away and from the database. If you’re a Rails dev, this is like stripping away the model’s access to Active Record. Instead, you abstract interfacing with the DB through a repository.
This doesn’t fully solve our issue of mutating state. We can still mutate an object’s guts.
In the above code, we no longer mutate state from an outside perspective. When we
.add() someone else’s todos to our list of todos, we are returned a brand new object instead of mutating the original list of todos in the previous object. This example is a bit contrived, but I hope that it gets you thinking about the state you are mutating and the side effects produced. Elixir is a concurrent language, state mutation can lead you to the danger zone, making bugs difficult to track down. To get more info, there is a great video on Functional Core, Imperative Shell that goes into more detail on the concept of isolating your Functional Core from your Imperative Shell.
Jose Valim said, In Elixir, due to its roots in functional programming, we often push towards explicit rather than implicit. Functional programming aims to be explicit by nature. This explicit behavior can sometimes make the code look arguably more verbose, but it’s a real win! Verbosity that gives insight and context wins over verbosity with syntactic sugar. Have you ever come across a method that gives no context? It’s just sitting in this black box. I begin asking myself Arnold Schwarzenegger-like questions to a class of kindergarteners. Who is this method’s Daddy and what does it do?
Functional programs use explicit functions and namespaces.
Services.UserClient.get(id) If we simply call
UserClient.get(id) without appending our
Services module, our code will blow up with a no module error. Contrast this to Ruby, we can have a method
handle_params sitting in our code. This
handle_params gives us no insight to its caller nor any context. This explicit approach is one we can use in OO programs like Ruby. How can we get our Ruby code to give us more insight and keep us from context switching just to try and find who this method belongs to? In Ruby, we can utilize the Metaclass.
Now we call
Handler.handle_params. Wow, super explicit! Dare I say functional? If you want to learn more about using the Metaclass in Ruby, Yehuda Katz has a great post Metaprogramming in Ruby: It’s all about the self .
I hope this blog encourages you to take a second look at what side effects may be creeping in as well as the implicit vs explicit behavior in your applications. Happy coding!