DEV Community

Discussion on: Why should we learn and use FP?

 
ruizb profile image
Benoit Ruiz

Webcomponents are a good example for this practice

I was more focused on the domain scope of the program, not the adaptation with the "outside world". But I never explicitly said it was my focus, so my bad. When integrating with an existing system, built on top of some class hierarchy, I guess one does not have a choice but to create a subclass. I believe this subclass should be used as an adapter or "glue" between the world of HTML elements, and the world of pure logic specific to the domain (here, our domain is classifying animals for example).

I personally do not really understand the whole discussion "FP is better than OO" and vice versa.

I don't either, both of these paradigms can be used to build software that works as intended. That's why I've explicitly said in the previous article of this series that FP is, in no way, a replacement to OOP. That being said, I can see some benefits coming from the functional approach, specifically in terms of composability made easy thanks to small, reusable units. I think it's harder to correctly find the appropriate class hierarchy to avoid duplication while still being flexible in terms of "mix of data and behavior" with the inheritance approach.

We can apply a "functional style" also in OO.

I don't think this example is relevant, because we are comparing composition with inheritance here, not "functional style" code with non-functional. Composition can be achieved using OOP without relying on "FP style", for instance:

class CanEat { public eat() {} }
class CanWalk { public walk() {} }
class CanBark { public bark() {} }

abstract class Animal implements CanEat {
  constructor(public readonly name: string, private readonly canEat: CanEat) {}
  public eat() { this.canEat.eat() }
}

class Dog extends Animal implements CanWalk, CanBark {
  constructor(public readonly name: string, private readonly canEat: CanEat,
              private readonly canWalk: CanWalk, private readonly canBark: CanBark) {
                super(name, canEat)
              }
  public walk() { this.canWalk.walk() }
  public bark() { this.canBark.bark() }
}
Enter fullscreen mode Exit fullscreen mode

Here we have a mix of inheritance and composition. The inheritance part is used for the semantics (a Dog is an Animal) and the mechanics (any Animal has to eat). The composition part is used to mix behaviors, depending on the Animal we are "building".

I do believe there are good cases where inheritance is more suited (cf. Composition vs. Inheritance: How to Choose? on /thoughtworks), but in general I think it's easier to build software using composition over inheritance. And inheritance doesn't exist in FP, so we don't get to choose anyway :)

Reusing code is one of the strongest motivations to use inheritance. So I really do not understand the "drawbacks".

I agree with you about reusability, but I believe it requires more effort to find the appropriate class hierarchy (hence the "drawback"). When new requirements emerge, modifying the class hierarchy will require more effort than creating new blocks out of existing smaller blocks, by composing them.

Given your last class hierarchy with the Orca: let's say I want an animal that can walk and play with fishes, but can't swim (e.g. it plays with them in shallow waters). You can do that with inheritance, but you'll have to update the existing one to adapt it for this new requirement. For example:

+ abstract class WalkingAnimal extends Animal {
+   walk() {}
+ }
- class Dog extends Animal {
+ class Dog extends WalkingAnimal {
    bark() {}
}

abstract class PredatoryFish extends Animal {
- swim() {}  
  playWithFish() {}
}

+ abstract class SwimmingPredatoryFish extends PredatoryFish {
+   swim() {}
+ }
+ abstract class WalkingPredatoryFish extends PredatoryFish {
+   walk() {} // code duplication? unless we use `walk = _walk.bind(this)` maybe?
+ }
- class Dolphin extends PredatoryFish {}
+ class Dolphin extends SwimmingPredatoryFish {}
+ class BearCub extends WalkingPredatoryFish {}
Enter fullscreen mode Exit fullscreen mode

(I guess you can probably come up with a better class hierarchy that involves fewer changes and less code duplication ^^)

With composition there's more flexibility, and there are only additions, leading to fewer changes:

+ const bearCub = () => ({
+   ...withName('bear cub'), ...canEat,
+   ...canWalk, ...canPlayWithFish
+ })
Enter fullscreen mode Exit fullscreen mode

But I can see the drawbacks of using global functions in large projects.

Definitely! It's only a matter of code organization. No matter the paradigm, importing tens of functions in the same module is a bad smell anyway :D

Thread Thread
 
efpage profile image
Eckehard

I fully agree that it is often more effort to use classes. It can be challenging to analyze your task and choose the right class hierarchy. So, there should always be a good reason to use classes.

But from my personal experience, the effort quickly pays back. If you made a bad decision in your design, it is easy to change the code without side effects. And in many cases, you do not need to care about implementation details. A well designed class should be usable as easy as a LEGO block.

Inside, classes are like separate programs. So, why not use the principles of FP to build classes? Maybe it is not necessary, but it´s possible and possibly helpful.