First I would like to say that I am probaby have a anti-OOP biais but not because of lack of knowledge, but because I’ve used both OOP and FP professionally for about 10 years each and if thought that OOP was better than FP I wouldn’t be there …(I’m not saying I’m an expert in both, just that I am (or was) as knowledgable in both).
As started off my carrier with C++ selling to people OOP concepts as encapsulation, interface to reluctant people telling me that C was enough and putting functions into structures didn’t change anything (it does). When I started FP (in 200*), the first thing I did was to trying to implement the concepts I knew worked and been consistently told to forget everything I knew. It took me a few years to unlearn those concept I was defending before. Apparently, 20 years later people have found clever ways to implement those them, recommending them to beginner deprive them of the chance to unlearn and try to think differently.
I’m not saying that some OOP things can’t be done, only that they shouldn’t be done just for the sake of it.
OOP has some basic principles (as encapsulation) and some tricks/pattern to work around its limitation.
For example encapsulation allows to hide the detail of the code to the caller. It has some benefit, but also some drawback, if things are hidden they are by definition hard to find. This therefore makes harder to debug, because it can be hard to find where is the faulty code. I had an old website written in PHP (drupal) finding anything was an absolute nightmare because the code representing a simple action would be spread into multiple hidden parts. When those parts work, everything is fine. When they don’t finding the culprit was hard. I never have that type of problem with Haskell.
Interface, in OOP was initially invented to circumvent a multiple inheritance issue (the infamous “diamond problem”). As we don’t have inheritence in Haskell, that particular notion of interface doesn’t need to be translated.
Moreover, inheritance and object can be translated differently in Haskell depending on the problems they represents. For example, sum types can the solution to inheritence. Another example is closure or currying.
Old OOP languages couldn’t do closure (or capture a variable and return a function using it). The solution was to create an object with a field(s) containing the captured variable(s). You could have a interface for it. A closure in Haskell is just a function, there is no need to create record for that, there is no need for interfaces either.
The same goes with thunks wich can modeled as objects with an interface.
My points is there is no one to one translation between OOP and FP so saying “here is the way to do X” works only for a limited subset of X.
Me last point about “shoehorn”. It wasn’t meant to be offensive (english is not my first-language), maybe retro-fitted is better. What I mean is that there will always be a qualitative difference between concepts whether they are supported natively by the language or not. If it’s built in the language, then it is usually less verbose and there is usually one prefered way of doing it. When it is not, there is usually some boiler plate and different schools of how to do it (hence all the “effect libraries”).
For example, interface is is built-in in C++ via abstract class (setting a function to = 0
). Haskell has built-in support for monads via do
syntax. You can do monads in C++ or javascript but you’ll end up with nested functions which are just awkward.