DEV Community

Discussion on: Why should we learn and use FP?

Collapse
 
efpage profile image
Eckehard

I don't think it makes sense to inherit properties and methods that won't be used anyway.

That is a common case in OO projects. Webcomponents are a good example for this practice:

class myNewComponent extends HTMLelement {
....
}
Enter fullscreen mode Exit fullscreen mode

You do not need to know all the class methods of HTMLelement, but you know the class will be part of the HTML ecosystem.

I personally do not really understand the whole discussion "FP is better than OO" and vice versa. FP is a coding sceme just like OO. If you see some drawbacks it might be from a wrong use of inheritance?

Let me show this from your examples. We can apply a "functional style" also in OO. Building a class for a single function is anyway useless. As long as we use pure functions, the code could look like this:

  // use pure functions here
  _walk() {...}
  _swim() {...}
  _bark() {...}


abstract class Animal {
  constructor(private readonly name: string) {}
  eat() {...}
}

class Dog extends Animal {
  constructor() { super('dog') }
  walk = _walk
  bark = _bark
}

class Dolphin extends Animal {
  constructor() { super('dolphin') }
  swim = _swim
  playWithPufferFish() {}
}
Enter fullscreen mode Exit fullscreen mode

Using global functions in OO always has a bad smell as you may easily run into naming conflicts, so this code would be considered "bad practice" in OO. But it may show that this is just a different syntax.

OO classes are mainly used to isolate your namespaces. playWithPufferFish() {} only exists inside your Dolphin class, so you can use the same name inside a different class without conflicts. But if you have more than one type of Fish that playsWithPufferFish and the code is the same, maybe you slide in an abstract class for all Predatory fishes, that contains this function:

  // use pure functions here
  _walk() {...}
  _swim() {...}
  _bark() {...}


abstract class Animal {
  constructor(private readonly name: string) {}
  eat() {...}
}

class Dog extends Animal {
  constructor() { super('dog') }
  walk = _walk
  bark = _bark
}

abstract class PredatoryFish extends Animal {
  swim = _swim
  playWithPufferFish() {}
}

class Dolphin extends PredatoryFish {
  constructor() { super('dolphin') }
}

class Orca extends PredatoryFish {
  constructor() { super('orca') }
  playsWithSeals(){...}
}
Enter fullscreen mode Exit fullscreen mode

Reusing code is one of the strongest motivations to use inheritance. So I really do not understand the "drawbacks". But I can see the drawbacks of using global functions in large projects.

Thread Thread
 
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.