DEV Community

Discussion on: OOP a software development mass psychosis

Collapse
 
wesen profile image
Manuel Odendahl • Edited

I'm not sure what the point of this article is besides being inflammatory. I am both a heavy user of functional programming and more traditional OOP languages.

My definition of OOP

There are many paradigms that get grouped under the name "OOP".

I think of OOP as:

  • "groups of method names are given a name",
  • "code that works on the same data can be reused", and
  • "groups of methods are kept close to the data they manipulate".

None of these are inherently bad, and they surface in different forms in functional programming languages too.

Task join/fork example in C++

You give the example of

join
   fork
      http.get:"https://gaiasoul.com"
   fork
      http.get:"https://servergardens.com"
Enter fullscreen mode Exit fullscreen mode

In a C++ framework I wrote (and I tend to be on the heavy-handed verbose side of the spectrum), I would write:

const auto ret = Tasks::join(
   m_Factories.HTTP->GET("https//gaiasoul.com"),
   m_Factories.HTTP->GET("https://servergardens.com")
).Start();
Enter fullscreen mode Exit fullscreen mode

If I wanted to push it, I could easily boil it down to

const auto get = [&](const string &s) { return m_Factories.HTTP->GET(s); };
const auto ret = join(
    get("https//gaiasoul.com"), 
    get("https://servergardens.com")
).start();
Enter fullscreen mode Exit fullscreen mode

which compiles to the exact same memory layout and binary code.

Because my HTTP factory implements an abstract virtual class, I can easily swap out different factories, say if I am running unit tests or decide to replace HTTP calls with some local IPC. I also get a concrete task object that I can pass around and introspect / monitor / listen to / cancel.

You might say that http.get : fun string -> bytes is more elegant than

class I_HTTPFactory {
public:
   virtual Task<bytes> GET(string s) = 0;
}
Enter fullscreen mode Exit fullscreen mode

but it is missing the point.

The value is that I get a clearly documented, easily extendable data structure that encapsulate what is needed to create thunks that ultimately return bytes. I can look at the class of my HTTPFactory instance and easily see that it contains say, a TLS certificate. I can also add a POST method, and make it obvious that both GET and POST belong together and reuse some of the same code and data.

These things are more opaque in a functional context.

Which is the best? None, they both work just fine.

OOP the strawman

Since you don't provide a concrete OOP API other than bringing up the strawman of needed an OutputFactoryMarshalerFactory to print out a single line of output, it is hard not to assume that you are arguing in bad faith. I can write

while (true) {
   println("I'M COOL");
}
Enter fullscreen mode Exit fullscreen mode

just as well. Bringing up LISP (all caps?), while Common Lisp has probably the most expansive approach to OOP of all the languages I know, strikes me as a bit odd as well.

Using C++ as an example, which is probably the language that I most often use to write what could be called "traditional OO", a class without virtual methods is arguably just a shortcut for a series of functions that take the same data structure as first argument. Once you introduce virtual dispatch, a class is just a way to combine a virtual dispatch mechanism with a data structure, give that group of dispatched functions a name, and make reuse easier. That's very similar to what many languages call "traits", arguably less flexible, but flexibility can often be a bad thing (say, if working in a big team with different skill-levels).

Data+Code centric patterns are useful

There's value in taking a data centric approach, especially in more resource constrained environments. I wouldn't know easily how much memory is allocated, when, where, by whom, and who owns it. In my C++, I know that I have 1 instance of Task_Join, 2 instances of Task_HTTPGet, and I can go look up the class to see that Task_Join keeps two pointers, and Task_HTTPGet a byte buffer, a state variable and a file descriptor. I know when they are created, I know when they are freed.

That data-centric OOP style might be less flexible than function-centric code when composing functions, but it's much simpler in many other regards. As with all code, it's about tradeoffs.

Collapse
 
polterguy profile image
Thomas Hansen

Thx for the snippet. It took me a couple of days to realise I don't need to prove anything at all, since your C++ example basically is all the proof I needed ...

Collapse
 
polterguy profile image
Thomas Hansen

In a C++ framework I wrote (and I tend to be on the heavy-handed verbose side of the spectrum), I would write

Interestingly, the following code snippet you provided ...

const auto ret = Tasks::join(
   m_Factories.HTTP->GET("https//gaiasoul.com"),
   m_Factories.HTTP->GET("https://servergardens.com")
).Start();
Enter fullscreen mode Exit fullscreen mode

... is arguably more in the style of FP than in the style of OO. However, you didn't provide the creation of your m_Factories instance, you didn't provide the wiring of your IoC container (assuming it was somehow dependency injected), etc. But nice code - You're obviously skilled :)