Polymorphism and Multimethods
Polymorphism
What is Polymorphism and what is it useful for?
In OOP (Object-Oriented Programming) polymorphism is well-known. It allows to separate an interface from multiple implementations that can have different behaviour.
Polymorphism comes from the greek polĂșs
(many) and morphe
(form). Multiple forms, makes sense.
Unless a variable, defined to an interface, is statically wired (using new
in Java) the concrete object referenced by the variable is not known at compile time. So, which polimorphic method of the interface is called is determined at runtime. This is called dynamic dispatch.
Let's make a simple example in Scala:
trait IPerson {
def sayHello()
}
class Teacher extends IPerson {
override def sayHello() {
println("Hello, I'm a teacher.")
}
}
class Pupil extends IPerson {
override def sayHello() {
println("Hello, I'm a pupil.")
}
}
class Student extends IPerson {
override def sayHello() {
println("Hello, I'm a student.")
}
}
This implements three different persons which say 'hello' in a different way. The beauty with this is that when you have an object that is of type IPerson
you don't need to know which concrete implementation it is. It usually is sufficient to know that it supports saying hello by calling sayHello
. This abstraction is great because it allows a decoupling of the interface and the concrete implementations which may even be defined in different areas or modules of the application sources.
OO languages like Scala, Java, C#, etc. combine data and behaviour in classes. An additional step in separation and decoupling is to separate data and behaviour. While that is possible in OO languages it is often not the norm, and once the language allows to mix data (state) and behaviour into classes it needs a lot of discipline to refrain from it.
Other languages separate data from behaviour naturally, which enables more decoupled design because data and behaviour can develop orthogonally. Many of those languages implement polymorphism with a concept called multimethods.
Multimethods
I choose Common Lisp as representative to show multimethods (because I like Lisps and this one in particular :), but also Groovy, JavaScript, Python or other languages support multimethods either natively or via libraries.
Single dispatch
In Common Lisp multimethods are implemented as generic functions. Common Lisp in general has a very powerful object system.
As a first step we create the classes used later in the dispatch:
(defclass person () ()) ;; base
(defclass teacher (person) ())
(defclass pupil (person) ())
(defclass student (person) ())
Similarly as the trait
in Scala we first create a generic function definition:
(defgeneric say-hello (person))
Now we can add the concrete methods:
(defmethod say-hello ((person teacher))
(format t "Hello, I'm a teacher."))
(defmethod say-hello ((person pupil))
(format t "Hello, I'm a pupil."))
(defmethod say-hello ((person student))
(format t "Hello, I'm a student."))
At this point we have a complete multimethod setup.
We can now call the methods and see if it works:
CL-USER> (say-hello (make-instance 'teacher))
Hello, I'm a teacher.
CL-USER> (say-hello (make-instance 'student))
Hello, I'm a student.
The runtime system will search for methods it can dispatch on based on a generic function definition. The method implementations can be in different source files or packages/namespaces which makes this extremely flexible. This lookup does come with a performance penalty though, but implementations often apply some kind of caching to mitigate this.
Multi dispatch
The above is a 'single dispatch' because the dispatching is based on a single parameter, the person class.
Multi dispatch can dispatch on multiple parameters. Let's extend the example a bit to show this:
(defgeneric say-hello (person time-of-day))
(defmethod say-hello ((person teacher) (time-of-day (eql :morning)))
(format t "Good morning, I'm a teacher."))
(defmethod say-hello ((person teacher) (time-of-day (eql :evening)))
(format t "Good evening, I'm a teacher."))
(defmethod say-hello ((person pupil) (time-of-day (eql :noon)))
(format t "Good appetite, I'm a pupil."))
(defmethod say-hello ((person student) (time-of-day (eql :evening)))
(format t "Good evening, I'm a student."))
Now we have a second parameter time-of-day
which doesn't represent a time, but whether it is morning, noon or evening (or some other time of the day). Since time-of-day
is not a class we have to use the eql
specializer for the dispatching, but it could also be another class.
CL-USER> (say-hello (make-instance 'teacher) :evening)
Good evening, I'm a teacher.
CL-USER> (say-hello (make-instance 'teacher) :morning)
Good morning, I'm a teacher.
CL-USER> (say-hello (make-instance 'pupil) :noon)
Good appetite, I'm a pupil.
So looks like that the dispatching works, by taking both parameters into consideration. Of course this works also with more than two parameters.
The generic functions in Common Lisp have a lot more features than those simple examples. For instance, with method specializers :before
, :after
or :around
it is possible to implement aspect oriented programming. However, this is not the topic of this post.
Conclusion
Multimethods and separating data from behaviour allows more decoupled code and a more data-driven programming paradigm. When the data is immutable we are closer in the realm of functional programming. Functional programming and data-driven programming have pros and cons which should be named and weighted when starting a new project.
-
[Polymorphism and Multimethods]
02-03-2023 -
[Global Day of CodeRetreat - recap]
07-11-2022 -
[House automation tooling - Part 4 - Finalized]
01-11-2022 -
[House automation tooling - Part 3 - London-School and Double-Loop]
02-07-2022 -
[Modern Programming]
14-05-2022 -
[House automation tooling - Part 2 - Getting Serial]
21-03-2022 -
[House automation tooling - Part 1 - CL on MacOSX Tiger]
07-03-2022 -
[Common Lisp - Oldie but goldie]
18-12-2021 -
[Functional Programming in (Common) Lisp]
29-05-2021 -
[Patterns - Builder-make our own]
13-03-2021 -
[Patterns - Builder]
24-02-2021 -
[Patterns - Abstract-Factory]
07-02-2021 -
[Lazy-sequences - part 2]
13-01-2021 -
[Lazy-sequences]
07-01-2021 -
[Thoughts about agile software development]
17-11-2020 -
[Test-driven Web application development with Common Lisp]
04-10-2020 -
[Wicket UI in the cluster - the alternative]
09-07-2020 -
[TDD - Mars Rover Kata Outside-in in Common Lisp]
03-05-2020 -
[MVC Web Application with Elixir]
16-02-2020 -
[Creating a HTML domain language in Elixir with macros]
15-02-2020 -
[TDD - Game of Life in Common Lisp]
01-07-2019 -
[TDD - classicist vs. London Style]
27-06-2019 -
[Wicket UI in the cluster - reflection]
10-05-2019 -
[Wicket UI in the Cluster - know how and lessons learned]
29-04-2019 -
[TDD - Mars Rover Kata classicist in Scala]
23-04-2019 -
[Burning your own Amiga ROMs (EPROMs)]
26-01-2019 -
[TDD - Game of Life in Clojure and Emacs]
05-01-2019 -
[TDD - Outside-in with Wicket and Scala-part 2]
24-12-2018 -
[TDD - Outside-in with Wicket and Scala-part 1]
04-12-2018 -
[Floating Point library in m68k Assembler on Amiga]
09-08-2018 -
[Cloning Compact Flash (CF) card for Amiga]
25-12-2017 -
[Writing tests is not the same as writing tests]
08-12-2017 -
[Dependency Injection in Objective-C... sort of]
20-01-2011