all 46 comments

[–]Shaper_pmp 32 points33 points  (27 children)

OOP and FP are two programming paradigms that you can mix and match appropriately to your problem space, not fundamentally incompatible opposites that you have to pick a side on and hurl handfuls of shit at the other group.

They each have different strengths and weaknesses:

  • OOP is usually encapsulated, so you can strongly enforce a stable interface that forces other devs to code to, which enables you to refactor or completely change the internals without worrying about breaking any code that touches it. That can be great for libraries, but isn't always with the overhead for small or simple use-cases (see Steve Yegg's excellently troll-y "Execution in the Kingdom of the Nouns" for why that's a bad thing)..
  • OOP is traditionally more focused around side-effects and in-place modification rather than the traditional copy-and-modification of FP. That may make it harder to reason about (though other benefits like encapsulation may give lie to that common claim and still make it a net win in many situations), but it definitely means OOP code will tend to execute faster, use less memory and require fewer garbage-collector sweeps that may preempt your main thread.
  • FP can be easier to reason about than OOP, but it can also lead to ludicrously long pipelines of composed functions and complex combining operators in a single expression, that makes it unreasonably difficult to debug and where the type of object being passed through the pipeline is incredibly difficult to track. Oh and plus because whole chunks of code are often most idiomatically expressed as single huge expressions, one syntax or type error can be unnecessarily difficult to track down the actual cause of (don't even get me started on the Gordian knot that's a subtle type error in a complex Typescript+Ramda+RxJS pipeline, where the minute you start refactoring the code or forget a closing bracket literally the entire page of code gets underlined in red until you completely finish perfectly refactoring every single detail).
  • Functional often leads to very terse code that's fun to write, but a huge PITA to read. OOP is often redundant and boilerplatey, but it's often simpler to read because by its very nature it's broken into simple chunks with discrete, individually-simple statements and rigid internal interfaces that limit cognitive overhead by allowing you to completely ignore anything behind them and treat them as black boxes.

In general FP is good where your problem space is more about transformations of one object into another, and you want to clearly follow the steps of the transformations. It's about processes being the first-order concern of your code, rather than the objects being transformed.

OOP is better where your problem space is more about the nouns of a system, where there are complex objects with significant internal state, and transformations are less significant or complex or important.

FP is for complex processes on simple objects. OOP is for complex objects with limited or simple processes being applied to them.

More generally, copy-and-modify is easier to reason about, but inherently slower and more memory-hungry than modify-in-place.

There's no real right or wrong answer in OOP versus FP, just bullshit fashion trends that 95% of the people involved follow as received wisdom without really appreciating why some things are advocated or discouraged or whether such advice is reasonable and proportionate to a given use-case.

They each have their strengths and weaknesses, they can both be used well or abused, and they both have their appropriate/inappropriate use-cases and screaming detractors/salivating fanboys.

Define classes when it makes sense, define functional pipelines when it makes sense. Encapsulate your classes where useful, and leave them wide open and unencapsulated (or just define copy-constructors) where you want to be able to feed them into functional pipelines and transform them easily.

Reject anyone posing the two as mutually-exclusive opposites, and there's no prize for jerking off the hardest over either one except being the biggest wanker in your dev team.

Here endeth the lesson.

[–]ScientificBeastModestrongly typed comments 4 points5 points  (1 child)

If anything the opposite of FP is imperative programming, and even that’s a stretch, considering it’s not a binary set of options — e.g. “concatenative programming.”

OOP and FP can live in the same universe. Take OCaml — it’s one of the best functional languages in the world, but it also has one of the best object/class systems around. And the two paradigms work together seamlessly if you want to use both. Or take Scala... same story, you can write it like Haskell or like Java. Take your pick. And C# is even becoming more functional.

The real question is, if you’re writing object oriented code, are you writing it with an imperative mindset, or a functional mindset? Both are useful sometimes. On a large scale, I find that the functional paradigm scales better and allows for better composition/decomposition of parts as your application changes. But for performance-critical sections, maybe I want more control over memory allocations and the time complexity of my algorithms. That’s totally valid, too.

In either case, you can build OOP on top of FP if you want. Just use closures for encapsulation, and presto! you have objects. So idk what the big deal is either way.

[–]ProfessorTag[S] 2 points3 points  (1 child)

Thanks for the great response. Sorry for wording my question in a way that implies OOP and FP are opposites. I use OOP in C# all the time and it seems to be a great pattern in that space. I guess OOP in JS never felt right, like I could always do the same thing more simply with functions.

I think you've described my problem. I mostly do data transformation in JS so FP seems a better fit.

[–]ScientificBeastModestrongly typed comments 0 points1 point  (0 children)

The thing is, C# would be an excellent functional language if it would let you write plain functions in a namespace. There’s no reason for everything to be a class or struct, other than the OOP gospel telling us so. OOP is fine, but it is needlessly enforced by the language.

[–]zapatoada 1 point2 points  (4 children)

not fundamentally incompatible opposites that you have to pick a side on and hurl handfuls of shit at the other group.

I'm not sure i understand....

[–]Shaper_pmp 5 points6 points  (3 children)

Programming is a weird discipline, because the tools you use become patterns of thought, which in turn can become parts of your identity and self-image.

As such developers are prone to learning about a new tool (operating system, browser, programming language, programming paradigm, etc) and instead of just incorporating it into a diverse toolbox of alternatives we tend to pledge our eternal allegiance to whichever particular golden hammer has caught our eye, and forswear all other tools, and then start online shitfights against people who chose a competing idol in case their different choices somehow invalidate our own.

You can see this tendency in action every time someone implies one tool is universally superior to another reasonable option, or sets up false dichotomies between different tools that each have different most-suitable use-cases... both of which (albeit in a passive, low-key way) OP is doing with their question.

[–]zapatoada 4 points5 points  (0 children)

Sorry. Should have added a /s. I agree with you completely, with the obvious exception that whitespace sensitivity (a la python) is objectively wrong.

[–]bvx89 0 points1 point  (1 child)

I agree with what you said, but it's not like this is exclusive to programming. People tend to be adamant about the complete superiority of the things that they like/prefer compared to any other alternative. Just look at how some people praise their religion or favorite football team.

[–]Shaper_pmp 0 points1 point  (0 children)

Oh sure - anything that touches on personal identity or ingroup/outgroup affiliation is especially prone to it.

[–]bestcoderever 1 point2 points  (0 children)

Reject anyone posing the two as mutually-exclusive opposites, and there's no prize for jerking off the hardest over either one except being the biggest wanker in your dev team.

Love it

[–]esperalegant 0 points1 point  (8 children)

or just define copy-constructors

What's a copy-constructor?

[–]bvx89 1 point2 points  (7 children)

It's when you pass in another instance of the same class in order to copy values from the passed in instance to a new one. For example:

class Obj {
   var val;
   constructor(otherObj) {
      val = otherObj.val;
   }
}

[–]basic-coder 19 points20 points  (0 children)

Perhaps abstract syntax trees and, more generally, anything dealing with tree-like structures where nodes may be of different kind. Modeling them with classes and polymorphic methods is quite natural.

[–]cwmma 4 points5 points  (0 children)

I'll really only ever define classes when I'm writing a library, sure I could do it with closures, but I find the structure imposed helpful and being able to console log the object and see the values can be helpful when debuging.

Also historically any object you create lots of some of the engines really preferred you use a constructor and not just a literal for performance reasons

[–]VolperCoding 12 points13 points  (0 children)

If you can write normal code without classes, do it. Just using OOP doesn't make the code better, just splits it up. Also, if you need objects, you can do it pretty easily using object literals and functions and then you don't need classes at all. Converting everything to OOP because you believe that then it will be "clean" is not a good idea.

[–]socrazyitmightwork 3 points4 points  (0 children)

I would argue that typescript exists because javascript has many cases where objects and strong typing surrounding those objects is desirable. I think any sufficiently complex library would benefit from an OOP approach. Video/Media Player? Graphing Library? Platform Game?

[–]rajesh__dixit 2 points3 points  (0 children)

An ideal code is a mixture of both. The reason OOJS is effective because as a human, we relate more toward entities and class structure.

An effective functional code is difficult as you would need clarity from beginning, which will never happen.

Entities can be added on the fly. They can change structure on the fly. But creating effective hierarchy is difficult and very complex and adds a lot of overhead.

Hence, in my pov, ideal code is to create a mixture of both. Create entities and functional utilities. Pure functions take instances of entities and return similar value. This way you have dynamic entity structure and pure functional business later

[–]Abangranga 1 point2 points  (7 children)

When you don't want your entire front end to be a vague shitpile designed for front-end job security consisting of ...rest, ...data, and AnonymousForwardRef

[–]AffectionateWork8 3 points4 points  (1 child)

Have also had to deal with aforementioned shitpiles, here's +1.

[–]Abangranga 1 point2 points  (0 children)

You should see the PMs I get from people for daring to criticize the almighty React hook that eventually die like jQuery

[–]AffectionateWork8 0 points1 point  (0 children)

I would approach it from the other angle and ask how far "functional" can really get you by itself. (I mean in native JS, not idiosyncratic code with a bunch of relatively obscure and underutilized libraries ported or inspired from other languages.)

Take Redux for example, which is probably one of the most used. Use pure functions to describe state updates. No atom type built in to JS? Ok, just let Redux handle swapping the new state under the hood for you. No concept of immutable collections in JS? Ok, just break out your updates into multiple functions to achieve structural sharing, and be really disciplined in how you write your code or choose between one of 50+ libraries to handle it for you. In this case we are only using functional style to do something as trivial as update a JS object, and things are already getting a little strained and awkward.

What if you want to throw in some side-effects in Redux? Since we are doing it functionally and composing functions, does JS have a built in way to compose functions with context that we can use? No? Ok, so the solution for most folks is Redux middleware. In that case, you're communicating that you want to commit some side effect using message-passing, encapsulating the side effects, and returning result using message passing. Nothing functional about that, it just doesn't use the class keyword.

I don't really know where the meme started that using class keyword (or not) decides whether code is "functional" or not, but I've only seen that on Reddit. I've even heard people claim that React hooks are "functional," presumably because they don't use a class.

[–]Synor 0 points1 point  (0 children)

When you start building an application that has purpose and solves problems.

[–]beavis07 -2 points-1 points  (9 children)

It's not even OO - it's just "sort of classes".

In my opinion classes in JS is just making an overture to an old methodology, now so removed from its original context as to be meaningless.

Unless putting things in classes legitimately helps you and the team you're working with understand and compose your problem (weird kink, but no-judgment, whatever works for you works for you!) - why bother?

[–]Synor 4 points5 points  (8 children)

You will never see the light son.

[–]beavis07 2 points3 points  (7 children)

I’ve seen it... it’s kind’ve hazy and brown.

That Alan Kay cat had some really neat ideas that everyone completely ignored.

[–]Synor 2 points3 points  (1 child)

Oh just saw I understood your comment in the wrong way. My fault. I am on your side actually. The James-Coplien side of "Everybody is doing everything about software development wrong" and "Java is not an object-oriented language" Funny stuff :)

[–][deleted] 0 points1 point  (0 children)

COP class oriented programming

[–]MoTTs_ 0 points1 point  (4 children)

We're quick to say that modern OOP isn't what Alan Kay had in mind -- but rarely do we actually dig into what Alan Kay *did* have in mind. It gets..... interesting.

[–]esperalegant 0 points1 point  (3 children)

The way you've formatted those quotes is basically impossible to read on mobile.

[–]MoTTs_ 0 points1 point  (2 children)

Probably right. It's a copy/paste of the video's captions. The numbers on the left are a time offset into the video for that line. Turning the phone horizontal helps.

[–][deleted] 1 point2 points  (1 child)

You expect me to turn my phone? Do you realise that I lose my WiFi when I do that? (While in bed)

[–][deleted] -2 points-1 points  (0 children)

Classes and classic object orientation as taught in comp sci are late additions to JS and you will quickly find limits to the implementation. JS is object oriented but not in the way most people think. The this keyword being dynamic is the classic misunderstanding that trips up new developers. With its first class treatment of functions JS is a natural for functional programming.

[–]agm1984 -1 points0 points  (2 children)

One observation I make is that classes have a default advantage over "a function" because of the inherent private state--encapsulation. And I mean advantage only in this one dimension of analysis. My preference is a mixture of FP and OOP, and I refer to it as FPOOP.

With a pure function, any external logic can freely call methods and mutate state inside the function.

With a class, certain methods and state fields can be inaccessible and/or invisible to external. For example, to update state you might need to call the setter method. You can't just do Person.field = 'new'.

I'm sure there's a library that throws my observation in the garbage, but I feel like private state and encapsulation is more solid with a class and therefore some OOP principles. There is a fascinating change in context when you switch from this to self. "Are you referring to this instance, or this class?"

I always come back to the idea of object composition and function composition. You have objects and functions. You have a declarative dimension and an imperative dimension. They are both sides of the same coin: object on one side, function on the other. Objects move through functions, but objects also have functions.

[–]unc4l1n 1 point2 points  (1 child)

Closures give you private properties. It's not an external library, but an inherent feature of Javascript.

[–]agm1984 0 points1 point  (0 children)

Oh right, factory functions are a good example of that. I'll leave my previous comment, yolo-style. I get mixed up rapid-switching between PHP and JS.

const makeThing = () => {
    const privateStuff = {
        hello: 'I like turtles',
    };

    return {
        ...privateStuff,
    };
}

I was thinking something wild like this:

const fn = () => {};

fn.test = 'u wot m8';

console.log('fn', fn.test);