How to use polymorphism in functional programming (with dynamic type system)?
Let's consider following example (first in OOP second in FP). The program is very simple - there are list of figures and we need to draw all of them, different figures use different drawing algorithms.
In OOP it can be done trivially, but how to do it in FP? Especially in languages with dynamic type system, like Scheme, Clojure (without static type resolving at compile time)?
I created simple code ( live version http://tinkerbin.com/0C3y8D9Z , press 'Run' button ). I used if/else switch in FP sample, but it's a very bad approach. How such problem can be solved better?
Samples are in JavaScript, but it's only for purpose of simplicity, it would be interesting to see a solution in any functional language with dynamic typing system.
OOP
var print = function(message){document.write(message + "\n<br/>")}
// Object Oriented Approach.
var circle = {
draw: function(){print("drawing circle ...")}
}
var rectangle = {
draw: function(){print("drawing rectangle ...")}
}
var objects = [circle, rectangle]
objects.forEach(function(o){
o.draw()
})
FP
var print = function(message){document.write(message + "\n<br/>")}
// Functional Approach.
var circle = {type: 'Circle'}
var drawCircle = function(){print("drawing circle ...")}
var rectangle = {type: 'Rectangle'}
var drawRectangle = function(){print("drawing rectangle ...")}
var objects = [circle, rectangle]
objects.forEach(function(o){
if(o.type == 'Circle') drawCircle(o)
else if(o.type == 'Rectangle') drawRectangle(o)
else throw new Error('unknown type!')
})
Your "FP" version is not what I consider the idiomatic FP example. In FP, you often use variants and pattern matching where in OOP you'd use classes and method dispatch. In particular, you only have one
draw
function that already does the dispatch internally:(Of course, that is JavaScript. In a functional language you typically have much more elegant and concise syntax for this, e.g.:
)
Now, the average OO aficionado will see the above code and say: "But the OO solution is extensible, the above is not!" That's true in the sense that you can easily add new shapes to the OO version and don't have to touch any of the existing ones (or their
draw
functions) when you do. With the FP way, you'd have to go in and extend thedraw
function, and all other operations that may exist.But what those people fail to see is that the converse is also true: the FP solution is extensible in a way the OO one isn't! Namely, when you add a new operation over your existing shapes then you don't need to touch any of the shape definitions, nor existing operations. You simply add another function, whereas with OO, you have to go and modify every class or constructor to include an implementation for the new operation.
That is, there is a dualism here in terms of modularity. The ideal, achieving simultaneous extensibility along both axes is known in literature as "the expression problem", and while various solutions exist (especially in functional languages), they are typically more complex. Hence, in practice you'll often want to decide for one dimension, depending on which dimension is more likely to matter for the problem at hand.
There are other advantages to the functional version. E.g it trivially scales to multi-dispatch or more complex case distinctions. It also is preferable when implementing an algorithm that is complicated and where the different cases are interrelated, so that you want to have the code in one place. As a rule of thumb, whenever you start using the visitor pattern in OO, a functional-style solution would have been more appropriate (and far, far easier).
Some further remarks:
This different preference in program organisation isn't the central idea of FP. What matters more is discouraging mutable state, and encouraging highly reusable higher-order abstractions.
The OO community has this habit of inventing new (buzz)words for every old idea. Its use of the term "polymorphism" (which is completely different from what it means elsewhere) is one such example. It says little more than being able to call functions without statically knowing what the callee is. You can do that in any language where functions are first-class values. In that sense, your OO solution is perfectly functional as well.
Your question has very little to do with types. Both the idiomatic OO and the idiomatic FP solution work in an untyped or typed language.
OO polymorphism is not a part of functional programming. However some functional languages (e.g. clojure) have oo polymorphism.
Another kind of polymorphism is multimethods
Or you just can use functional style
In several languages designed primarily for functional programming, there are ways to achieve (ad-hoc, as it is called) polymorphism, though they differ from what you call polymorphism. Haskell, for example, has type classes (not to be confused with classes from classical OOP):
(Scala has objects, and also implicits which apparently parallel or even surpass type classes.) You can then implement any number of independent types, and make each an instance of the type class (again independently, e.g. in an entirely different module):
This is probably what you'd use in Haskell, if you actually needed that degree of extensibility. If you have a finite number of cases belonging together (i.e. you could use
sealed
classes in the OOP alternative), you'd likely use algebraic data types (see below).Another way is to do just what your JS snippet does (which is, by the way, not what you'd do to achieve polymorphism if you had any number of objects of each type, and this version has the same problem): Embed a function that does the polymorphic behavior in each object. In a sense, your "OOP" snippet is already functional.
Though in a static language, this does not permit different objects to have different attributes.
A more bearable alternative to the bunch of conditions you present, but nevertheless similar and with the same limitation (it's hard to add another shape), is pattern matching with algebraic data types. Other answers on Stackoverflow have explained these well, I'll just give this concrete example in that style:
In Clojure there are Protocols which provide basically the same ad-hoc polymorphism as Haskell's type classes:
You can also extend an existing type:
And then you can apply the draw method to some instances
There really isn't anything non-functional about your first code sample. Even in languages that have no support for object orientation, you can do the same thing. That is you can create a record/structure/map that contains functions and then put those into your list.
In your simple example where there's only one function, you could also just create a list of functions directly, like
objects = [drawCircle, drawRectangle]
.