DEV Community

Cover image for Do the SOLID principles apply to Functional Programming?
Patricio Ferraggi
Patricio Ferraggi

Posted on

Do the SOLID principles apply to Functional Programming?

If you are interested in reading this article in Spanish ๐Ÿ‡ช๐Ÿ‡ธ, check out my blog:
The Developer's Dungeon

Yesterday I was browsing one of the online developer communities where I usually hang out and I noticed there was this super intense debate about what writing good code is all about. As usual, the terms Design Patterns, Clean Code, and SOLID where being thrown around a vicious fight of programming terms. It was clear that the audience was mainly Object-Oriented programmers. Suddenly, someone started talking about Functional Programming, me as an OOP programmer who is in his path to absorb as much as I can from the Functional way, I got intrigued.

This person argued that SOLID and Design Patterns are Object-Oriented Programming(OOP) stuff and they don't relate well with Functional Programming(FP).

This argument got stuck inside my head so I decided to write about it to express my own personal opinion. Since I have two different opinions on SOLID and Design Patterns, today, I am gonna cover only SOLID, I may cover Desing Patterns in the near future though.

Let's start with some introductions.


What is SOLID?

SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible and maintainable

They were first introduced by Robert C. Martin in the early 2000s in his paper Design Principles and
Design Patterns
and they are the following:

  • Single-Responsibility Principle

  • Openโ€“Closed Principle

  • Liskov Substitution Principle

  • Interface Segregation Principle

  • Dependency Inversion Principle

How are they used in OOP and FP?

Let's go one by one and let's see how they apply to both paradigms.

Single-Responsibility Principle

"An object should only have a single responsibility, that is, only changes to one part of the software's specification should be able to affect the specification of the object."

Traditionally when people talk about this principle they think about classes (although the original idea comes from UNIX development), they think about extracting behavior into multiple classes and handling a proper separation of concerns.
Although functional programming languages don't have classes the same principle holds true. Functions should be small reusable pieces of code that you can compose freely to create complex behavior.

This can be extracted to almost anything, once your functions are small, the modules where they are located they should also form a cohesive closure that does only one thing and does it well.

As long as your function or class or module has only one reason to change then you are applying this principle.

Openโ€“Closed Principle

"Software entities ... should be open for extension, but closed for modification."

This principle is usually instantly related to inheritance. A well-defined parent class that holds functionality and children of this class extend or reuse the mentioned functionality. In reality, it just means that we should be able to reuse and extend code without having to modify the original implementation.

Instead of using inheritance, Functional Programming achieves this by using two tools. Composition to create new behaviors from previously defined functions and higher-order functions to change functionality at runtime, btw if you are interested in reading more about these topics you can check my series Functional Programming for the object-oriented developer.

Liskov Substitution Principle

"Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program."

Again when people generally think about this principle the first idea that comes to their head is that if the parent class has some behavior, their children should not break that behavior, but this is not the only applicable case, LSP also applies in case we use generic or parametric programming where we create functions that work on a variety of types, they all hold a common truth that makes them interchangeable.

This pattern is super common in functional programming, where you create functions that embrace polymorphic types (aka generics) to ensure that one set of inputs can seamlessly be substituted for another without any changes to the underlying code.

Interface Segregation Principle

"Many client-specific interfaces are better than one general-purpose interface."

This is an easy one, but many people, including the ones that brought the topic to my attention, get too attached to the word "interface" and they automatically refer to the concept of interfaces in languages like C# or Java, they think that if you don't have interfaces then this principle cannot be applied.

In reality, every interaction between components is done by an interface. When you use functions from a module, you are using the disposed interface of that module, even if we are in a dynamically typed language, that interface still exists. The point of this is that the way you create modules(or classes or interfaces or API's or whatever) needs to be cohesive, you should provide one clear way of doing things instead of many, and you should expose only what is necessary for the users to perform the specific task.

Dependency Inversion Principle

"One should depend upon abstractions, [not] concretions."

In languages like C#, this is achieved by using two tools. One is to create interfaces to define contracts of a predefined functionality. The other is to use dependency injection so that users of that functionality don't manually instantiate the concrete class, instead, they receive an instance of the interface through their constructor and they just call the appropriate methods on the instance.

In functional programming, abstractions are the default way of handling code, functions are abstractions too, especially in functional programming where we care more about the "shape" of the data instead of to which specific type they are attached to. This creates the possibility to freely change the implementation at runtime by passing functions as parameters to other functions or even returning functions as results from the computation.


I hope you liked my take on SOLID and Functional Programming. If you would like to see a direct comparison between implementations of Design Patterns in OOP and FP please let me know below in the comments ๐Ÿ˜„

As always, if you liked this post please share it on social media.

Oldest comments (18)

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
patferraggi profile image
Patricio Ferraggi

I am glad you liked it. The idea has been in my mind for a while now so I will probably write an article about it next week or so ๐Ÿ˜€

Collapse
 
patferraggi profile image
Patricio Ferraggi

I am glad it helped, now I don't remember if they are explained in Clean Code or if I originally read about them somewhere else. I am gonna check and report back.

Collapse
 
davidyaonz profile image
David Yao

Nice explanation.

TBH, it was pretty hard to put all these theories together, but you made it easy to understand. Thanks.

Collapse
 
patferraggi profile image
Patricio Ferraggi

Thanks for reading it, I am glad you liked it. Yeah, it is more of my own analysis on how these principles apply so It can be hard to put into words.

Collapse
 
kspeakman profile image
Kasey Speakman • Edited

You have done a good job with the explanation. I think the bigger picture is that the SOLID principles are a specific application of computer science principles to OO. For example, Separation of Concerns and polymorphism are not specific to OO. And SOLID simply directs some of those more general concepts at OO.

I do not think the SOLID principles apply to FP, but instead the more general CS concepts apply. I am being slightly disagreeable here for a practical reason. When I was a new functional programmer, I tried to apply OO principles to learning FP. And it made learning FP difficult. In fact I gave up the first attempt. I find that the best way to approach learning FP is to treat it as structured programming with a few new rules such as: immutability, deterministic functions, and using expressions instead of statements.

For a more humorous take on this topic, check out this great presentation by Scott Wlaschin.

Which has this hilarious slide that saw some circulation.

nine OO patterns listed on left with equivalent FP patterns on right all saying functions

Collapse
 
iquardt profile image
Iven Marquardt

put very diplomatically!

Collapse
 
patferraggi profile image
Patricio Ferraggi

Hey Kasey, thanks for writing this. I heard that about learning functional programming many times, unfortunately for me I still haven't found a way of letting go of everything related to oop, probably with time the functional side will get better. Right now I am in that case you describe. I already watched that talk from Scott is super good though I haven't got into F# yet.

Collapse
 
kspeakman profile image
Kasey Speakman

Thanks for the comment. You can retain all your OO knowedge while learning FP, but maybe just put it in a box to the side. :) It is helpful to look at it as going back to basics. You just have data structures and functions, maybe like Go. That is a good starting place for the journey. I wrote an introductory article here. It is for F#, but the principles should apply to OCaml, Haskell, or Reason.

Collapse
 
zilti_500 profile image
Daniel Ziltener

From my experience, really the only way in is learning-by-doing, and best using a language that simply doesn't allow you (or makes it uncomfortable for you) to fall back into OOP thinking. For me that language was Clojure, for some it is Haskell or F#. I never used or looked at F#, but I heard good about it, too. I think learning one of these three and applying them in a real-world scenario will just ease you into FP as a "byproduct".

Thread Thread
 
patferraggi profile image
Patricio Ferraggi • Edited

Thanks for the comments, I agree with everything you said. I also like the approach of learning with a language that doesn't let you fall back. I am also learning with Clojure. I tried Haskell but it was too much for me. So far Clojure has been a very pleasant experience but I still have a long way to go. I am still with the basics. I am using a book called "Functional programming for the object oriented programmer" by Brian Marick, is quite good

Collapse
 
iquardt profile image
Iven Marquardt

I consider it amazing that you get that much attention with this topic. You clearly hit the bull's eye! I will add a corresponding OO-related chapter to my FP online course.

Collapse
 
patferraggi profile image
Patricio Ferraggi

I am glad I was of help ahah. Could you please post your online course when it is done? I would be interested in checking it ๐Ÿ˜€

Collapse
 
iquardt profile image
Iven Marquardt
Collapse
 
zilti_500 profile image
Daniel Ziltener

A very good explanation! I never really consciously thought about SOLID. I do also think that the principles it describes do apply to Functional Programming, but in a very natural way. I'd go so far as to say that SOLID is a guideline of applying a few principles from the functional world in the object oriented world.

Collapse
 
hugecoderguy profile image
Christian Kreiling

What a fabulous article, thanks for sharing! I have recently been trying to bridge the gap of OOP Design Patterns and functional programming myself, so I'm interested to read your next article. Maybe I can give some food for thought based on what I've been thinking about!

I program web backends in Java at my day-job but prefer the immutable & functional language Elixir for personal projects. They are vastly different languages but I find surprising parallels in how they're used. The primary Java backend I contribute to leverages the Visitor pattern to extend the behavior of objects that hold data, without having to define the behavior in their class definition. It seems like a good approach because it's made the service extensible in a variety of ways.

The more I worked within the bounds of the Visitor pattern, something became more apparent: it seems that the Visitor Pattern is actually just achieving one of the core principles of functional programming, which is to decouple structured data from its behavior! Another couple of OOP Design Patterns that I think achieve functional principles:

  • Observer/Publish-Subscribe pattern: Pub-Sub is a pretty functional concept in itself because you effecitvely register function callbacks with a centralized event bus object.
  • Strategy pattern: this pattern favors composition over inheritance for making a program extensible. The concept of composition is superfluous to functional programming!

I therefore think that some OOP Design Patterns, when applied, are actually making programs written in OOP languages more functional... I think it's really hard to think of OOP and FP as black and white; you can write FP code in an OOP language, and you can also write OOP code in an FP language. It's not worth arguing about which paradigm a program is written in or isn't, it's about which paradigm the program is more of. I'd like to hear your thoughts on this :)

Collapse
 
patferraggi profile image
Patricio Ferraggi

Hey Christian how are you? Thank you very much for that answer. It really have me something to think about. I never thought about the case of the visitor pattern, I am gonna have to check it better before my next article. I did saw the similarities you mentioned on the strategy pattern and the observer pattern, though in functional programming the benefits of the strategy pattern are much easier to achieve due to first class functions. I also see some similarities between function composition and the composite pattern. I definitely believe that a lot of the design patterns were created to give oop languages the flexibility that functional languages get for free without losing the control over data and state (at least in theory ahaha)

Collapse
 
hugecoderguy profile image
Christian Kreiling

Doing great, you've had me thinking about OOP design patterns over the last few days. I think we're saying the same thing wrt the strategy pattern; it's a means of taking some functions and composing their inputs & outputs through a centralized code module, which is what all programs written in an FP language look like. What similarities do you see between Function Composition and Composite Pattern? I see the Composite Pattern more as a means of representing a tree structure in a strongly-typed OOP language, is this what you mean when you say Composite Pattern?
Ooh, I really appreciate your observation "a lot of the design patterns were created to give oop languages the flexibility that functional languages get for free without losing the control over data and state." I've been trying to put that into words for awhile now