DEV Community

Cover image for Polymorphism in Javascript
mahdi
mahdi

Posted on • Updated on

Polymorphism in Javascript

Summary:

Polymorphism is a fundamental concept in object-oriented programming (OOP) languages, where "poly" refers to many and "morph" signifies transforming one form into another. In the context of programming, polymorphism enables the same function to be called with different signatures, allowing for flexibility and adaptability in code design.

Understanding Polymorphism:

In real-life scenarios, individuals often exhibit polymorphic behavior. For instance, consider a boy who can simultaneously act as a student, a class monitor, or even a team captain. Each role necessitates different actions and responsibilities, showcasing the versatility and adaptability inherent in polymorphism.

Similarly, a girl can seamlessly move between different roles such as a student, an employee, and a citizen. Depending on the context, she performs different tasks and fulfills distinct responsibilities, demonstrating the essence of polymorphism being capable of assuming diverse forms and functionalities based on the prevailing circumstances.

Polymorphism empowers developers to design software systems that are resilient, extensible, and capable of accommodating evolving requirements. By leveraging polymorphic principles, programmers can create code that efficiently adapts to changing contexts, thereby enhancing the robustness and flexibility of their applications.

In JavaScript, polymorphism is achieved through prototype inheritance and method overloading.

Features of Polymorphism

Polymorphism, a cornerstone of object-oriented programming (OOP), enables a single function or method to exhibit different behaviors based on the context in which it is invoked. Let's explore the features of polymorphism with a clearer and more understandable example.

Example: Animal Sounds

Consider a scenario where we have different types of animals, each capable of making a distinct sound. We'll create a base class Animal with a method makeSound(), and then we'll create subclasses for specific types of animals, each overriding the makeSound() method to produce the unique sound of that animal.

// Base class representing an Animal
class Animal {
  makeSound() {
    console.log("Some generic animal sound");
  }
}

// Subclass for Dogs
class Dog extends Animal {
  makeSound() {
    console.log("Woof! Woof!");
  }
}

// Subclass for Cats
class Cat extends Animal {
  makeSound() {
    console.log("Meow! Meow!");
  }
}

// Subclass for Cows
class Cow extends Animal {
  makeSound() {
    console.log("Moo! Moo!");
  }
}

// Create instances of different animals
const dog = new Dog();
const cat = new Cat();
const cow = new Cow();

// Invoke the makeSound() method on each animal
dog.makeSound(); // Outputs: Woof! Woof!
cat.makeSound(); // Outputs: Meow! Meow!
cow.makeSound(); // Outputs: Moo! Moo!
Enter fullscreen mode Exit fullscreen mode

Explanation:

In this example, we have a base class Animal with a generic makeSound() method. Subclasses such as Dog, Cat, and Cow extend the Animal class and override the makeSound() method with their specific sound implementations.

  • Polymorphic Behavior: Despite invoking the same method makeSound(), each object (dog, cat, cow) exhibits different behavior based on its type. This is polymorphism in action, where the method behaves differently depending on the object's actual class.

  • Flexibility and Extensibility: With polymorphism, we can seamlessly add new types of animals (subclasses) without modifying the existing code. Each new subclass can define its own behavior for the makeSound() method, enhancing the flexibility and extensibility of our system.

  • Code Reusability: By defining a common interface (makeSound() method) in the base class Animal, we promote code reuse across different subclasses. This reduces redundancy and promotes a modular and maintainable codebase.

In essence, polymorphism allows us to write code that is adaptable, reusable, and easily extensible, making it a powerful tool in object-oriented programming.

Types of Polymorphism in JavaScript

JavaScript, being a versatile language, supports different types of polymorphism, each offering unique benefits and use cases.

1. Ad-hoc Polymorphism:

Ad-hoc polymorphism allows functions to behave differently based on the types or number of arguments passed to them. It is often implemented through method overloading or conditional logic within functions.

Example:

Consider a function calculateArea() that calculates the area of different shapes based on the number of arguments provided. If two arguments are passed, it calculates the area of a rectangle. If only one argument is passed, it calculates the area of a circle.

function calculateArea(shape, arg1, arg2) {
  if (shape === "rectangle") {
    return arg1 * arg2;
  } else if (shape === "circle") {
    return Math.PI * Math.pow(arg1, 2);
  } else {
    throw new Error("Unsupported shape");
  }
}

console.log(calculateArea("rectangle", 5, 3)); // Outputs: 15 (Area of rectangle)
console.log(calculateArea("circle", 4)); // Outputs: 50.26548245743669 (Area of circle)
Enter fullscreen mode Exit fullscreen mode

In this example:

  • The calculateArea() function behaves differently based on the value of the shape parameter.
  • If shape is 'rectangle', the function calculates the area of a rectangle using two arguments (arg1 and arg2).
  • If shape is 'circle', the function calculates the area of a circle using one argument (arg1).

Advantages:

  • Simplicity: Provides a straightforward way to define multiple behaviors within a single function based on input parameters.
  • Flexibility: Allows for versatile function usage with different argument combinations, enhancing code adaptability.
  • Readability: Promotes code readability by encapsulating conditional logic within the function, making it easier to understand and maintain.

2. Parametric Polymorphism:

Parametric polymorphism, also known as generic programming, allows functions and classes to operate on values of unspecified types. It is enabled by using type parameters that can be instantiated with various concrete types.

Example:

// Generic function to return the length of an array or string
function getLength(input) {
  return input.length;
}

console.log(getLength([1, 2, 3])); // Outputs: 3
console.log(getLength("Hello")); // Outputs: 5
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • Reusability: Promotes code reuse by allowing the creation of generic functions and classes that can operate on multiple data types.
  • Type Safety: Enhances type safety by specifying constraints on type parameters, reducing the risk of runtime errors.
  • Abstraction: Encourages abstraction by separating algorithmic logic from specific data types, leading to cleaner and more modular code.

3. Subtype Polymorphism:

Subtype polymorphism allows objects of different classes to be treated as objects of a common superclass. It is implemented through inheritance and method overriding, where subclasses provide specific implementations for inherited methods.

Example:

// Base class representing a Shape
class Shape {
  draw() {
    console.log("Drawing a shape");
  }
}

// Subclass representing a Circle
class Circle extends Shape {
  draw() {
    console.log("Drawing a circle");
  }
}

// Subclass representing a Square
class Square extends Shape {
  draw() {
    console.log("Drawing a square");
  }
}

// Polymorphic behavior
const circle = new Circle();
const square = new Square();

circle.draw(); // Outputs: Drawing a circle
square.draw(); // Outputs: Drawing a square
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • Flexibility: Allows for the creation of a unified interface for diverse objects, enabling seamless interchangeability and flexibility in code design.
  • Extensibility: Facilitates easy extension of functionality by adding new subclasses with specialized implementations while maintaining compatibility with existing code.
  • Polymorphic Dispatch: Enables polymorphic dispatch, where the correct method implementation is dynamically chosen at runtime based on the object's actual type, enhancing runtime flexibility and adaptability.

Dynamic Polymorphism:

Explanation:

Dynamic polymorphism refers to the ability of an object to decide at runtime which method implementation to invoke based on its actual type. This is achieved through method overriding in subclass implementations.

Example:

Consider a scenario where we have a base class Shape with a method draw(), and subclasses Circle and Square that override the draw() method to provide specific implementations. At runtime, the correct draw() method is dynamically chosen based on the type of shape being drawn.

// Base class representing a Shape
class Shape {
  draw() {
    console.log("Drawing a shape");
  }
}

// Subclass representing a Circle
class Circle extends Shape {
  draw() {
    console.log("Drawing a circle");
  }
}

// Subclass representing a Square
class Square extends Shape {
  draw() {
    console.log("Drawing a square");
  }
}

// Dynamic polymorphic behavior
const shapes = [new Circle(), new Square()];

shapes.forEach((shape) => {
  shape.draw(); // Outputs: Drawing a circle, Drawing a square
});
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • Flexibility: Allows for runtime determination of method implementation, enabling adaptable behavior based on object types.
  • Extensibility: Facilitates adding new subclasses with specialized behavior without modifying existing code, enhancing code maintainability and scalability.
  • Dynamic Dispatch: Enables polymorphic dispatch, where the appropriate method implementation is selected dynamically at runtime, promoting runtime flexibility and adaptability.

Multiple Inheritance:

Explanation:

Multiple inheritance allows a class to inherit properties and behaviors from more than one superclass. While JavaScript does not support multiple inheritance in the traditional sense, it can be simulated using mixins or object composition techniques.

Example:

Consider a scenario where a class StudentAthlete inherits from both Student and Athlete classes, thereby inheriting attributes and methods related to academic performance as well as athletic abilities.

// Mixin for Student-related functionalities
const StudentMixin = {
  study() {
    console.log("Studying...");
  },
};

// Mixin for Athlete-related functionalities
const AthleteMixin = {
  exercise() {
    console.log("Exercising...");
  },
};

// Class representing a Student Athlete
class StudentAthlete {
  constructor(name) {
    this.name = name;
  }
}

// Object composition to simulate multiple inheritance
Object.assign(StudentAthlete.prototype, StudentMixin, AthleteMixin);

const studentAthlete = new StudentAthlete("Mahdi");

studentAthlete.study(); // Outputs: Studying...
studentAthlete.exercise(); // Outputs: Exercising...
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • Enhanced Flexibility: Allows for the creation of complex class hierarchies and facilitates code reuse by inheriting features from multiple parent classes.
  • Selective Inheritance: Enables selective inheritance of specific functionalities from different parent classes, promoting fine-grained control over class behavior.
  • Composition over Inheritance: Favors composition over traditional inheritance, promoting code modularity and reducing the complexity associated with deep class hierarchies.

Polymorphism in Functional Programming:

Explanation:

While polymorphism is commonly associated with object-oriented programming, it also exists in functional programming paradigms. In functional programming languages like JavaScript, polymorphism can be achieved through higher-order functions, function composition, and parametric polymorphism.

Example:

In functional programming, a higher-order function that takes other functions as arguments exhibits polymorphic behavior by accepting functions with different signatures.

// Higher-order function to perform an operation on two numbers
function operate(num1, num2, operation) {
  return operation(num1, num2);
}

// Functions representing different operations
function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

console.log(operate(3, 4, add)); // Outputs: 7
console.log(operate(3, 4, multiply)); // Outputs: 12
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • Modularity: Polymorphism in functional programming promotes code modularity by separating concerns and encapsulating reusable behavior within functions.
  • Expressiveness: Enables the creation of concise and expressive programs by abstracting common patterns into higher-order functions and function compositions.
  • Functional Composition: Facilitates function composition, where smaller, reusable functions are combined to create more complex behaviors, promoting code reuse and readability.

Design Patterns Leveraging Polymorphism

Design patterns are reusable solutions to common problems encountered during software design and development. Polymorphism plays a crucial role in many design patterns, enabling flexible and extensible software designs. Let's explore some common design patterns that leverage polymorphism:

1. Strategy Pattern

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It allows the algorithm to vary independently from clients that use it.

Example:
// Strategy Interface
class PaymentStrategy {
  pay(amount) {}
}

// Concrete Strategies
class CreditCardPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`Paid $${amount} using Credit Card`);
  }
}

class PayPalPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`Paid $${amount} using PayPal`);
  }
}

// Context
class ShoppingCart {
  constructor(paymentStrategy) {
    this._paymentStrategy = paymentStrategy;
  }

  checkout(amount) {
    this._paymentStrategy.pay(amount);
  }
}

// Usage
const cart = new ShoppingCart(new CreditCardPayment());
cart.checkout(100); // Outputs: Paid $100 using Credit Card

cart._paymentStrategy = new PayPalPayment();
cart.checkout(200); // Outputs: Paid $200 using PayPal
Enter fullscreen mode Exit fullscreen mode

In this example, the ShoppingCart class utilizes a payment strategy that can be dynamically set to either CreditCardPayment or PayPalPayment, demonstrating how polymorphism allows interchangeable behavior at runtime.

2. Template Method Pattern

The Template Method Pattern defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.

Example:
// Abstract Class defining Template Method
class Meal {
  prepare() {
    this.prepareIngredients();
    this.cook();
    this.serve();
  }

  // Abstract methods to be implemented by subclasses
  prepareIngredients() {}
  cook() {}
  serve() {}
}

// Concrete Subclass
class Pizza extends Meal {
  prepareIngredients() {
    console.log("Prepare pizza ingredients");
  }

  cook() {
    console.log("Cook pizza in oven");
  }

  serve() {
    console.log("Serve pizza hot");
  }
}

// Concrete Subclass
class Pasta extends Meal {
  prepareIngredients() {
    console.log("Prepare pasta ingredients");
  }

  cook() {
    console.log("Boil pasta in water");
  }

  serve() {
    console.log("Serve pasta with sauce");
  }
}

// Usage
const pizza = new Pizza();
pizza.prepare();
// Outputs:
// Prepare pizza ingredients
// Cook pizza in oven
// Serve pizza hot

const pasta = new Pasta();
pasta.prepare();
// Outputs:
// Prepare pasta ingredients
// Boil pasta in water
// Serve pasta with sauce
Enter fullscreen mode Exit fullscreen mode

Here, the Meal class defines a template method prepare() that orchestrates the preparation, cooking, and serving of a meal. Subclasses such as Pizza and Pasta provide specific implementations for each step, demonstrating how polymorphism allows customization of behavior while maintaining the overall algorithm structure.

3. Visitor Pattern

The Visitor Pattern represents an operation to be performed on the elements of an object structure. It allows adding new operations without modifying the classes of the elements.

Example:
// Visitor Interface
class Visitor {
  visit(element) {}
}

// Concrete Visitor
class TaxVisitor extends Visitor {
  visit(element) {
    if (element instanceof Liquor) {
      return element.price * 0.18; // Liquor tax rate: 18%
    } else if (element instanceof Tobacco) {
      return element.price * 0.25; // Tobacco tax rate: 25%
    } else if (element instanceof Necessity) {
      return element.price * 0.05; // Necessity tax rate: 5%
    }
  }
}

// Visitable Interface
class Visitable {
  accept(visitor) {}
}

// Concrete Visitable Elements
class Liquor extends Visitable {
  constructor(price) {
    super();
    this.price = price;
  }

  accept(visitor) {
    return visitor.visit(this);
  }
}

class Tobacco extends Visitable {
  constructor(price) {
    super();
    this.price = price;
  }

  accept(visitor) {
    return visitor.visit(this);
  }
}

class Necessity extends Visitable {
  constructor(price) {
    super();
    this.price = price;
  }

  accept(visitor) {
    return visitor.visit(this);
  }
}

// Usage
const items = [new Liquor(20), new Tobacco(30), new Necessity(50)];
const visitor = new TaxVisitor();

const totalTaxes = items.reduce((acc, item) => acc + item.accept(visitor), 0);
console.log(`Total Taxes: $${totalTaxes.toFixed(2)}`);
Enter fullscreen mode Exit fullscreen mode

In this example, the Visitor Pattern is used to calculate taxes for different types of items without modifying the item classes. The TaxVisitor class defines specific tax calculations for each item type, demonstrating how polymorphism allows adding new operations without altering existing class structures.

These design patterns showcase how polymorphism enables flexible and extensible software designs by allowing interchangeable behaviors, customized algorithm steps, and dynamic operations on object structures.

Conclusion:

Polymorphism, a fundamental concept in object-oriented programming (OOP), empowers developers to create flexible, extensible, and adaptable software systems. By allowing objects to exhibit multiple forms and behaviors based on their context, polymorphism promotes code reuse, modularity, and maintainability, thereby enhancing the overall quality of software designs.

Through various forms such as ad-hoc polymorphism, parametric polymorphism, subtype polymorphism, and dynamic polymorphism, developers can leverage polymorphic principles to achieve diverse functionalities and address a wide range of programming challenges. Additionally, polymorphism extends beyond traditional OOP paradigms and finds application in functional programming, where higher-order functions, function composition, and type inference facilitate polymorphic behavior.

Design patterns such as the Strategy Pattern, Template Method Pattern, and Visitor Pattern exemplify how polymorphism can be effectively utilized to create robust, scalable, and maintainable software architectures. These patterns demonstrate the practical application of polymorphism in solving common design problems, such as algorithm variability, code reuse, and dynamic behavior customization.

In JavaScript, polymorphism is achieved through various mechanisms, including prototype inheritance, method overriding, higher-order functions, and object composition. By understanding and leveraging these techniques, JavaScript developers can design elegant and efficient solutions that leverage the power of polymorphism to meet the evolving needs of modern software development.

Ultimately, polymorphism serves as a cornerstone of software design, empowering developers to build resilient, adaptable, and future-proof applications that can seamlessly evolve and scale with changing requirements and technological advancements.

Top comments (31)

Collapse
 
danbars profile image
Dan Bar-Shalom • Edited

I assume that not on purpose, but your example of man and woman polymorphism is somewhat misogynous. The man is a team captain while the woman is a mother and a wife.

Regardless, nice article.
Here's a small function that can help create method overloading based on signature matching

function overload(patterns, defaultFn) {
    return (...args) => {
        for (const pattern of patterns) {
            if (pattern.match(args)) {
                return pattern.fn.apply(this, args)
            }
        }
        return defaultFn.apply(this, args)
    }
}
Enter fullscreen mode Exit fullscreen mode

And this is how you can use it:

const add = overload([{
    match: args => args.length === 1 && typeof args[0] === 'number',
    fn: (x) => x*2
}], (a,b) => a+b)

add(3) // 6
add(3,2) // 5
Enter fullscreen mode Exit fullscreen mode
Collapse
 
m__mdy__m profile image
mahdi

I really apologize if you think my article is misogynistic, I didn't mean it that way, but I just gave some examples to better understand polymorphism.

Collapse
 
danbars profile image
Dan Bar-Shalom

I don't get offended personally by things that people say, and I wasn't looking for an apology.
Yet, words have the power to define the reality in which we live.
If women are defined as "mothers" or "wives" it puts them in a certain spot. Even though you didn't mean it that way, those words help fixating this reality. The problem, imho, is not that your example uses those women roles, but the unbalanced examples. You didn't choose "husband" and "father" for the boy, which implies that boys can be students and captains. And yet, for the women, you chose 2 out of 3 examples which defines them as their place in the family and not as what they do.

If this resonates with you, you can just change the example :)

Thread Thread
 
m__mdy__m profile image
mahdi

I made it, do you think it's good?

Thread Thread
 
danbars profile image
Dan Bar-Shalom

Awesome! Thanks :)

Collapse
 
efpage profile image
Eckehard

Awsome!

Collapse
 
abidullah786 profile image
ABIDULLAH786

I think the code example for overloading is correct, we know that for overloading the class should have more than one functions either with difference in number of parameters or at least type of each parameters (or sequence).

But as JavaScript is not type strict language so we can not make a function over loading with types difference the only thing we can do is to play with number of parameters. In you cases you have 2 parameters for both add() functions and i think it will always call the 2nd add function no matter either the numbers are integers or strings

Your exam

class Calculator {
  // Method Overloading: Add with two parameters
  add(a, b) {
    return a + b;
  }

  // Method Overloading: Add with string concatenation
  add(str1, str2) {
    return str1 + str2;
  }
}

const calculator = new Calculator();

console.log(calculator.add(2, 3)); // Outputs: 5 (Addition of two numbers)
console.log(calculator.add("Hello", " World"));
Enter fullscreen mode Exit fullscreen mode
Collapse
 
skyjur profile image
Ski • Edited

Yes it's correct. Js doesn't have method overloading. Also method overloading is not polymorphism have no idea why it's even here. Author doesn't really know js well. The mixin example for example is quite a terrible pattern to use and author doesn't mention anything about downsides only "hey you can do this to do multiple inheritance" but actually no you can't unless you want to shoot your self in the foot.

Collapse
 
m__mdy__m profile image
mahdi

Can you tell me which part of the article is wrong?
And I have not said anything about polymorphism

Thread Thread
 
skyjur profile image
Ski • Edited

In functional programming languages like JavaScript

JavaScript is not functional programming language. It's been OOP language from it's very inception days and it has always remained such. Many of it's APIs of built-in collections are modifying-in-place and not immutable but in functional language it's necessary that all apis are immutable. It is possible to write functional inspired architecture in JS but that doesn't change the fact that JS is not functional language. The fact that you use a stone to hammer a nail doesn't turn a stone into a hammer it's still a stone.

Compile-time Polymorphism (Method Overloading):
Explanation:
Compile-time polymorphism, or method overloading, occurs when multiple methods in a class have the same name but different parameter lists

Method overloading is not polymorphism. And JavaScript does not have method overloading. You have brought up method overloading several times in javascript and it's not clear why, since JavaScript does not support it, and also, because this has nothing to do with polymorphism.

It really sounds to me here that you wrote half your article with ChatGPT pulling in all the nonsense generated by language models without doing any filtering. More than half what ChatGPT produces is just utter garbage and if you use it as a tool to help your writing then it's your diligence to fix the garbage before putting it into articles.

Also if you made significant use of ChatGPT you should specify it for transparency so that readers are more easily aware that some of it is not your opinion but just language model garbage.

Thread Thread
 
Sloan, the sloth mascot
Comment deleted
 
m__mdy__m profile image
mahdi • Edited

I am learning OOP and OOP concepts
You can see the concepts I chose in this repository:
OOP

I sent my article to chatgpt after finishing it and he gave me this offer and I did it without any special filtering. I'm sorry. I will try not to repeat it
And also I got OOP concepts from him, if possible take a look at it to fix its problems.

Thread Thread
 
oculus42 profile image
Samuel Rouse

It's perfectly acceptable to use AI tools for improving your article, but communicating that it was used is important.

Dev.to provides guidelines for communicating that an article contains or was assisted by AI tools.

Thread Thread
 
oculus42 profile image
Samuel Rouse • Edited

Additionally, I disagree with ski's assessment:

JavaScript is not functional programming language. It's been OOP language from it's very inception days and it has always remained such.

While JavaScript did start as an Object-oriented language, it has incorporated a number of Functional Programming capabilities, and is listed as one of the many "impure" Functional Programming languages, along with several others that started out as imperative or OO.

Functional programming can and does happen in JavaScript these days, but there is an important distinction between it and purely functional languages.

Thread Thread
 
skyjur profile image
Ski • Edited

Note how in your list contains even C++ and Java are listed as "impure functional". Definition that they use to consider language as "impure functional" language doesn't strike me as a practically useful way to categorize. Any language claiming functional capabilities at very least should strives to provide most functional style apis and have at least some optimizations that work well for code that is written in functional style. Scala is good example of language that is not purely functional but has capabilities necessary to write functional code. If you have runtime that isn't well optimized for functional-style code (js/java) and if you have APIs that are not functional, then where do you find functional programming in your "impure functional" language whilst everything in language is encouraging to not write functional code?

Also if you think React is functional - I have a news for you - it is not even remotely functional. The so called "functional" components are not functional at all instead they are OOP in different syntax that uses JS's "function". The useState() hook is same concept as private state encapsulation with added observer/reactability to it when changes to state are done. Javascript has a somewhat weird approach to do OOP but there is very little functional code in js and often any attempt to write functional code in js results in rather terrible performance. Most APIs in JS are mutable, JS doesn't have tail recursions or pattern matches. The only "functional" feature in js are closures, but closures it self is not really strictly functional concept - it would functional if it were immutable closures but in js closures can modify outer scope thus even closure implementation is not remotely functional. In the end there is just almost nothing in JavaScript that help you write functional code. On top of that JS engines optimize for OO performance not the functional techniques because DOM is OO.

Thread Thread
 
oculus42 profile image
Samuel Rouse

TC39 – like most governing bodies – don't create breaking changes to the spec, so those mutable, OO underpinnings in most languages are there to stay, but the trend to adding functional capabilities is very strong.

JavaScript has been adding an increasing number of immutable methods since the ES2015 update – even while it also adds private class members. These are most heavily focused around the array methods, but extending to to things like `structuredClone as well. The pipe operator is still in proposal.

In the last few revisions they've introduced immutable versions of older methods, like .toSorted() to replace the mutating Array.prototype.sort.

The runtime is fairly well optimized for many functional programming tasks because working with data arrays and objects rather than class instances is itself substantially faster than pointers and indirect references. It's part of the reason that Java is on the "impure" list... lambdas in Java add a little functional style to save a lot of performance and developer overhead without having to jump to writing Scala.

While there are lots of limitations to FP in JavaScript – it doesn't have native currying, but .bind added partial application a decade ago – numerous libraries have provided both Functional and FRP solutions on top of JavaScript for quite some time.

React does support "Pure Functional Components" but they are barely mentioned. Hooks are not at all functional and that misunderstanding – the "magic" of hooks hiding instances – is probably one of the biggest headaches of modern React.

It's increasingly possible to leverage the declarative, functional programming style in JavaScript and doing so is not that difficult. It doesn't provide the safeguards of a purely functional language, but it can make a big difference.

Thread Thread
 
skyjur profile image
Ski • Edited

I really just can't agree that functional programming is performant or to any acceptably decent level in js.

Firstly problem of performance. Any time you want to do functional code you end up doing array copies, object copies, it all is quite slow. Not only the fact of allocating these objects take extra time but also garbage collector works much harder to free up all the extra space that was produced. Thus it's just not possible to use functional style code to implement anything that needs to really take the most out of performance. And UI code frequently needs top performance as it includes transitions/animations, real-time interactive elements that need to feel to user reactive and instant. IMHO language that claims to be functional should do a lot more optimization techniques on compiler level and immutability here plays extremely important role because only immutability unlocks these compile time performance improvements in functional languages and JavaScript everything is mutable thus it simply will never be fast if you try to write functional code. Not to mention the fact that DOM api which is the very spine of applications is all not functional and again simply will never be functional.

Second problem is readability and ergonomics. Functional apis in javascript often happen to just be poorly readable and/or debuggable. For example it's very hard to debug and understand multi level closures and situations where functions are being passed as arguments - it's just hard to figure out where function definition/implementation is coming from. In contrast if you pass instance, and call method, it's usually possible to use "search implementation" tool that is well supported in IDEs. Inline functions also are often hard to debug in sense that it's impossible to place breakpoints accurately or add log statements without refactoring function from x => y into x => { return y } and breaking them apart to make the span through multiple lines. Also thre are many cases where readability is horrendous one example is reduce() where initial state value is being passed as last argument making it hard to read and wrap the head around.

Also I wouldn't agree that EcmaScript is adding functional capabilities in any reasonable speed - toSorted() was added in 2023! That is some 2 decades too late.

Thread Thread
 
oculus42 profile image
Samuel Rouse

First, I really appreciate the thought and detail you put into your comment. I understand and even shared many of your concerns in the past, but the engines and tools have made significant leaps. I have seen the JS engines increase performance orders of magnitude in array operations on the same hardware. Back in 2010 Nicholas Zakas wrote High Performance JavaScript, which warned about the performance impact of traversing nested objects, thanks to inefficiencies in prototype retrieval. That was a sub-microsecond operation by 2012.

Optimizations in the compilers hidden behind JavaScript Engines produce "hidden classes" - basically structs to optimize memory allocation and storage of collections - and use static code analysis to determine if objects are mutated. Engines can provide performance close to that of immutable data when you "follow the rules". You can choose not to, and suffer the performance impacts of doing so, but there are performance benefits to treating data as though it were immutable, even though the language allows mutation.

Tools like JSHint and ESLint have long stopped us from using the more error-prone parts of JavaScript, and there are rules for mutation that can prevent a lot of those potential performance and maintainability issues.

The development tools are getting better, too. Dev Tools in Chromium browsers now let you place breakpoints at expressions within a line once it is set, allowing us to stop or log execution without modifying the source like we once did. It's not perfect, but it's a big improvement over the previous state, which required us to modify our source to properly debug.

The functional style definitely takes getting used to. The initial value of .reduce() is optional, as it will provide the first array entry as the "accumulator". Skipping the initial value can help, but it can also be confusing for people who expect it. fold in Elm also puts the initial value after the operation, but it accepts the array as the final argument, where .reduce() is on the array prototype, so the array is implied.

I agree that there are readability challenges in some functions/implementations, and they mostly comes from JavaScript's OOP origins. A lot of this can be overcome with thin utility functions that wrap or mask the prototype operations from the consumer so they can operate more like pure functional languages.

The addition of more functional capabilities, while slow, is happening. Functional programming in JavaScript isn't perfect, but it's not only possible, it's useful. I've used it for about a decade, now; first with libraries, and more and more with plain JavaScript, in production applications for e-commerce and banking.

Thread Thread
 
skyjur profile image
Ski • Edited

Regarding performance, writing immutable code often is the source of performance issues. For performance sensitive situations modifying arrays/objects is the only way. If we want to enable functional programming it's essential to provide low level data structure APIs that are functional and well optimized to avoid unnecessary data copies.

Regarding readability "takes getting used to" this is simply never an argument. It's just acceptance that it's bad. If something takes less time getting used to and can achieve similar result then it is simply better. If something takes more time getting used to and achieves same thing then it simply is worse.

There is one way to greatly improve reduce() could just be having two functions, one reduce((a,b)=>...) and another as reduceFrom(initial, (a,b)=>...). Having initial value as last argument is not just something that takes time to get used to. It's just bad because before you read code it's necessary to know what the first value is in order to understand the code. Yet first value comes last. It is handicapping readability and speed at which we can read and understand code. Whilst it's small thing the fact that it affects every time every single person it has large cumulative cost mental cost.

But again reduce doesn't only suffer from poorer readability it's also poorer performance. Functional language compiler or runtime should be able to unwrap simple reduce implementations into stack-less execution if it can't then it's simply is not an adequate functional runtime. If it doesn't and if it is always stack push and stack pop on every iteration as it is in JavaScript then that means functional programming is a 2nd class citizen. Of course it is always possible to do many different things with any programming language as 2nd class citizen but that always comes with a lot of overhead.

Collapse
 
efpage profile image
Eckehard • Edited

Object pascal uses overloading also for simple functions. You can implement the same function multiple times with different parameters, the compiler determines the right function by checking number and type of parameters used by the caller.

We can do something similar building a polymorphic function using parameter type checks to implement different behavoir insinde function. If - for some reason - we do not want to change the initial function, we could do something like this (which is a bit awkward):

let say = (x) => { console.log("This is " + x) }

let s1 = say
say = (x) => {
  if (typeof x !== "number") return s1(x)
  console.log("This is Number " + x)
}

let s2 = say
say = (x) => {
  if (typeof x !== "object") return s2(x)
  console.log("This is Object " + JSON.stringify(x))
}

// call polymorph functions
say("Hallo")
say(3)
say({ Test: "TEST" })
Enter fullscreen mode Exit fullscreen mode

running code here

Do you know any better way to achieve this in JS?

Collapse
 
m__mdy__m profile image
mahdi • Edited

Yes, there is a better way:

// Define different strategies
const strategies = {
  default: (x) => console.log("This is " + x),
  number: (x) => console.log("This is Number " + x),
  object: (x) => console.log("This is Object " + JSON.stringify(x))
};

// Function implementing the Strategy Pattern
function say(x) {
  let strategy = strategies.default; // Default strategy

  // Check the type of x and assign the appropriate strategy
  if (typeof x === "number") {
    strategy = strategies.number;
  } else if (typeof x === "object") {
    strategy = strategies.object;
  }

  // Execute the chosen strategy
  strategy(x);
}

// Call the polymorphic function
say("Hello");
say(3);
say({ Test: "TEST" });
Enter fullscreen mode Exit fullscreen mode

running code here

Collapse
 
efpage profile image
Eckehard

Beside the lack of a default value I would prefer this version:

// Define different strategies
const strategies = {
  string: (x) => console.log("This is " + x),
  number: (x) => console.log("This is Number " + x),
  object: (x) => console.log("This is Object " + JSON.stringify(x))
};

// Function implementing the Strategy Pattern
function say(x) {
  strategies[typeof x](x);
}

// Call the polymorphic function
say("Hello");
say(3);
say({ Test: "TEST" });
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
m__mdy__m profile image
mahdi

It is really a beautiful code

Thread Thread
 
artydev profile image
artydev

Indeed :-)

Collapse
 
cardoprimo profile image
DDKP

"Certainly! Here's an improved example for ad-hoc polymorphism"
At least proof read your AI content lmao

Collapse
 
m__mdy__m profile image
mahdi • Edited

thank you for your attention
Yes, it was my mistake, sorry, I fixed it, but it doesn't mean that the content was generated by AI, I used AI to improve my content :))

Collapse
 
alyrik profile image
Kiryl Anokhin

I'm afraid "Compile-time Polymorphism (Method Overloading)" does not apply to JavaScript and the example code doesn't work as expected.

Collapse
 
m__mdy__m profile image
mahdi

It doesn't work in js. It is true because js does not support overriding overload methods like other languages ​​(such as Java).
But what do you mean the code doesn't work?

Collapse
 
alyrik profile image
Kiryl Anokhin

I mean that the 2nd method is always called in that example. So this sentence is not true: "The compiler determines the appropriate method to call based on the provided arguments".
Basically, this example just demonstrates how "+" operator works :)

Thread Thread
 
m__mdy__m profile image
mahdi

Yes, it's true. It was my mistake :) I corrected it (actually, I removed that part from the article because many people told me it was wrong!)

Collapse
 
rmiah209 profile image
Raheem Miah

Great, insightful article! Helped me understand polymorphism much better, being a student as well.