Are you seeing OOP as an alternative paradigm to multiple dispatch, or would they somehow integrate?
It is a nice suggestion. In my opinion, with multiple-dispatch the functionality of a struct is very similar to OO, because with the type you define your functions and there is not limitation.
It is true that:
- There is not private data, you can access to all fields without limits, but there is not in Python, neither. Sometimes I miss it, but the ecosystem in Julia enforces flexibility, and it could be a limitation.
- The inheritance. Actually, in OO the first thing one in real-work designs is that many times the inheritance produce many problems, and it should be used better the composition. See: Composition vs. Inheritance: How to Choose? | Thoughtworks. Actually, I have spent many years doing a course in design OO for programmers (I could talk during hours about that ). It is true that the lack of inheritance could be seen as a limitation, but there are still interesting packages to “simulate it”, as @heliosdrm suggested.
To summarize, it is true that Julia is not OO, but it is not clearly a limitation. Actually, using types and subtypes the functionality is very similar. The missing part is inheritance, but maybe it is good, because it is a tool that many times is overused, and badly used (modern languages like Rust avoid it). However, in the cases you want inheritance, there are available packages that could allow to “simulate it”.
@DNF I think @dmolina is spot on. Multiple dispatch covers nicely the polymorphism aspect of OOP. I “grew up” with Fortran modules/types/generic functions and C++ overloads, virtual functions and all the OOP for runtime and templates for compile-time polymorphism. I see merit on each paradigm (especially in large code bases), but also bloated designs from misusing them, so all bad is not exclusive to one paradigm.
As pointed out, the inheritance aspect is the one missing (also encapsulation but a simple convention like in Python might do the job e.g. _privateMember). My point is that perhaps it might be worth having the conversation to standardize inheritance as there are many ways to (using @dmolina words) “simulate it” using 3rd part implementations (that is already exposing a need and might be some merit to discuss). Also, composition is not inheritance, inheritance follow “is-a”. while composition follows “has-a”, a mix of the 2 is possible in any language that enables OOP. In fact, the majority of OOP scientific frameworks use the 2 in a mutually inclusive (not exclusive) way. I see them as orthogonal concepts. BTW, Rust allows interface inheritance it doesn’t avoid it completely. Could something like the latter possible in Julia?
To be clear, lack of OOP is not a limitation to do things…C and Fortran work fine, but there is tons of boilerplate that need to be added that is already in C++, so many large project opt for C++ for that reason. I guess I am also trying to understand the long term view of the Julia language. Thanks for chiming in.
The “Julia way” to imitate this is using “traits”
See also packages to facilitate their usage, like
Some other thoughts:
- It is true that “is-a” is not “has-a”, but a composition “has-a” usually is more flexible, for instance, to allow modifying dynamically the behavior, when the inheritance does not allow it. In my opinion, composition is more powerful and useful (actually, through the years, I have regretted many times using inheritance several months later by that limitation). This is specially important in scientific/research software, because they tend to need more flexibility than other types of software.
- Interface inheritance is, in my opinion, actually obtained with subtypes, but it could be nice to be able to check easily that a function is actually defined for a structure (but I think it could be done with a macro and a package, without changing the language). Actually, BinaryTraits.jl allow us to define formal interfaces, see
@implement
and@check
. - About Traits packages, my personal favorite package is https://github.com/tk3369/BinaryTraits.jl. But I have to recognize that I still need more examples to actually “figure out” when to use it.
I remember large codebases with long inheritance chains, so to understand a single class functionality you should look at several different classes, interfaces and so on.
So, I prefer not to use inheritance in C++, even if there should be one.
There is often a misunderstanding between two different points of inheritance:
- Write reusable code once.
- API or “contract” between different devs (or dev teams) to simplify their reuse of different parts of complex system.
I think inheritance is bad on both points, because multiple dispatch is more practical for (1), and common abstract interfaces / APIs are more practical for 2.
In my understanding, the type system provides some kind of inheritance (“is-a” pattern).
E.g.
abstract type A end
struct B <: A end
struct C <: A end
function foo(x:: A) ...
The method foo defined for the supertype A is “inherited” by the subtypes B and C, unless explicitly overwritten.
The main differences to classical OOP inheritance are that:
- A parent type must be abstract and therefore cannot be instantiated
- Only behavior (functions) can be inherited, not data fields.
For example, if you define a struct as a subtype of AbstractArray, you get a lot of behavior “for free”. The prerequisite for this is that you define the interface functions for it, see Interfaces · The Julia Language
Currently, these interfaces are explicitly given only in the documentation. Imho it would be nice to specify and enforce (the mandatory ones) them directly in the language.