5

Is it possible to have any kind of polymorphism with Functional Programming in JavaScript?

I like FP, but when I want to use JS I can't figure out how to support polymorphism in JS other than using classes/prototypes.

For example how is it possible to implement toString with FP in JS?

With OOP I can simply overload toString so that object.toString() executes the toString code specific to the object or its prototype.

Ali Shakiba
  • 20,549
  • 18
  • 61
  • 88
  • 1
    I am not sure your question is clear. I don't understand whether you want to implement function that returns a string or what? – Martin Chaov Jan 15 '18 at 06:08
  • 1
    Give more context, what do you want to achieve. ! – rijin Jan 15 '18 at 06:52
  • 1
    See [here](https://stackoverflow.com/questions/3379529/duck-typing-in-javascript) for duck typing. – ceving Jan 15 '18 at 07:33
  • @AliShakiba - I know what toString does, I don't understand what you want to do. – Martin Chaov Jan 15 '18 at 08:48
  • There is nothing forbidding your function to take different types of data as argument and do internal type checking in order to achieve what you want. Said function could internally transfer you to others like: objectToString, numberToString and so on. You will be calling something like: toString(arrg), and it will internally call the correct one. – Martin Chaov Jan 15 '18 at 08:51
  • 1
    In Haskell there is one instance of `show` for each type that is convertible to string. The compiler picks the right one (or yells at you when there isn't one). So the compiler effectively contains a data structure that maps known data types to known instances of `show` and runs a compile-time type check to pick the suitable function. You can't do that at compile time in JS, but you certainly can create such a data structure (a magic global) and mimic the behavior at run time. – Tomalak Jan 15 '18 at 08:57
  • @Tomalak Javascript's prototypes are sufficient to overload function names and relate them to particular value constructors. You don't need some magic global. But since JS is untyped, you don't get the type guarantees type classes usually provide. –  Jan 15 '18 at 10:07
  • @MartinChaov right, but that means implementing polymorphism myself. – Ali Shakiba Jan 15 '18 at 10:07
  • This is not polymorphism. JS doesn't have function signature, so it cannot identify between functions with the same name and different arguments. So you need to implement function that takes all types as input and internally checks and calls what's relevant. Then returns the string. Polymorphism is concept unique to the classical OOP, you don't have it in Prototypal OO and Functional. – Martin Chaov Jan 15 '18 at 10:20
  • @MartinChaov Polymorphism is not unique to OO, for example operator/function overloading is also polymorphism. – Ali Shakiba Jan 15 '18 at 10:34
  • @MartinChaov JS is not untyped. It's just not statically typed. You can determine if an object is an instance of String or Date or any custom constructor function you create. This is enough information to pick the matching "toString" function implementation, but of course it would have to be done all at run-time. – Tomalak Jan 15 '18 at 10:40
  • @Tomalak, exactly what I sad :) Further more I never said anything about types. I said functions doesn't have signatures, so you cannot do overloading. – Martin Chaov Jan 15 '18 at 11:18
  • @AliShakiba, are you possibly referring to shadowing and delegation? – Martin Chaov Jan 15 '18 at 11:18
  • @MartinChaov Agreed, you can't do overloading. There needs to be a single `toString` function that has an internal look-up based around `instanceof` checks. It would be rather crude. – Tomalak Jan 15 '18 at 12:36
  • @Tomalak, I didn't say it is going to be pretty :) But that's the language specifics. I still don't understand what is the problem to use a function that tries to call this method on the passed argument and return the result. ```function toString(arrg){ return arrg.toString() || null; }``` Maybe something ugly like that. – Martin Chaov Jan 15 '18 at 12:57
  • I guess the OP is aware of that option, but distinctly wants to avoid it. The question seems to me more academic than pragmatic. – Tomalak Jan 15 '18 at 13:02

2 Answers2

3

Javascript is an Untyped Language

No, Javascript doesn't have a notion of polymorphism, because it is an untyped language. Simply put, polymorphism means that a strict type system is less strict under controlled conditions, that is it doesn't lose type safety by behaving polymorphically.

Untyped language is somewhat simplified, though. From the perspective of a static type system Javascript has one huge union type and a value or its underlying expression can take any of its representations during runtime (variables can even adapt to different types during their existence). This sort of typing is also called dynamical typing.

Dynamically typed languages have introspection to check the type of a value at runtime. But this means are limited. You can't introspect the type of a function, for instance, as long as it is not completely applied.

However, functional programming in the original sense means to work with lots of small, specialized first and higher order functions, which are declared in curried form. This approach leads to partially applied functions all over your code. The problem is now that you not only have to deduce the types of your initial functions but also the intermediate types of partially applied ones. This is going to be tough quickly:

// what's the type of this function?

const comp = f => g => x => f(g(x));


// and this partially applied one?

const inc = n => n + 1;
comp(inc);


// and even worse:

comp1 = comp(comp);
comp2 = comp(comp) (comp);

I'm sure I've lost you somewhere between the lines. It takes a lot of time to deduce these types from the mind. And should it really be your responsibility as a developer to act like a compiler? I don't think so.

Alternative Solutions to the Problem

Fortunately, the Javascript community has been actively developing solutions to this type of problem.

A static type checker on top of the language

Flow and TypeScript are static type checkers that try to add a type system to Javascript retrospectively. I personally don't think that this is a promising approach, because usually you design the type system first when you create a new language. Javascript can perform side effects literally everywhere, which makes it really hard to create a sound and reliable type checker. Check out the issues in the Flow repository to get your own picture.

Degrade Javascript as compile target

Ya, this headline may be a bit opinion-based, but that's what it feels like to me. Elm, purescript, Facebook's Reason are representatives of this approach. Well, if you want to give up on Javascript, these are reasonable possibilities. But which horse to bet on? And is the fragmentation of the Javascript ecosystem really desirable? Or do we want a community that is dependent on vendors like Facebook? I can't really answer this question, because I am highly biased, as you're about to see.

Runtime Type Checker

Heads up: This is a shameless plug!

As a dynamically typed language Javascript ships with mature introspection capabilities. Along with ES2015 proxies we have all we need to build a virtualized runtime type checker. Virtual means in this context, that it is pluggable, that is you can switch it on and off. A runtime type system needs to be pluggable, because it has a major impact on performance and is only needed during the development stage.

I've been working on such a type checker for several months now and it's been an exciting journey so far. ftor is far from beeing stable but I believe the approach is worth exploring.

Here is the comp combinator from above as a typed version with type hints (TS is just an internal Symbol that holds the current signature of a type and you can use it to request this signature for debugging purposes):

import * as F from ".../ftor.js";


F.type(true);


const comp = F.Fun(
  "(comp :: (b -> c) -> (a -> b) -> a -> c)",
  f => g => x => f(g(x))
);


const inc = F.Fun(
  "(inc :: Number -> Number)",
  n => n + 1
);


comp(inc) [TS]; // "(comp :: (a -> Number) -> a -> Number)"


const comp1 = comp(comp),
  comp2 = comp(comp) (comp);


comp1 [TS]; // "(comp :: (a -> b0 -> c0) -> a -> (a0 -> b0) -> a0 -> c0)"


comp2 [TS]; // "(comp :: (b1 -> c1) -> (a0 -> a1 -> b1) -> a0 -> a1 -> c1)"

comp1's intermediate type signature tells you that it expects...

  1. a binary function
  2. a value
  3. an unary function
  4. and another value

That is you would apply it like comp1(inc) (1) (add) (2) (3).

comp2's intermediate type signature tells you that it expects...

  1. an unary function
  2. a binary function
  3. two values

And you would apply it like comp2(inc) (add) (2) (3). So comp2 is actually useful, because it allows us to apply a binary function as the inner function of the composition.

Admittedly, these type signatures aren't easy to read if you are not familiar with them. But in my experience the learning curve is rather short.

ftor supports parametric and row polymorphism, but currently not ad-hoc.

1

Polymorphism in the context of functions means a function call with the same name but with different argument types calls different functions based on the argument types. This means a dispatching has to be done based on the argument types. You can do this kind of dispatching in JS at runtime using the typeof and instanceof syntax. But this is considerded beeing too slow for general purpose use. Most modern functional approches (ML, Haskell) inplement a compile time dispatching using type inference. This has only an impact on the compilation time but not the execution time. But JavaScript is a traditional functional language like Scheme with a fixed object system and without macros.

So the answer is: no it is not possible to have any kind of polimorphism in JS.

ceving
  • 21,900
  • 13
  • 104
  • 178