Would a tail-recursive function like they write in Elixir/Erlang be efficient in Julia?
Julia doesnât have tail call elimination so if you recurse too far you will overflow the stack.
Despite the fancy name, the expression problem is definitely a stumbling block youâve encountered many, many times in object-oriented programming, wether you knew what it was or not. From the OOP side (as opposed to the functional language perspective, where it is also a problem but looks quite different), it is simply the problem of trying to add a new operation to existing types. For what itâs worth, I go through this in depth in the video I linked above, but some people prefer written explanations. The core problem with class-based OOP is that methods live inside of classes and everyone has to agree on what methods make sense to add to each class. You may think, âWhat is the problem? I have added methods to classes all the time?â In your own code that you fully control, sure, if you want a new method, just open up the class definition and add the method. The problem occurs when, as happens in the real world, there are many people sharing and reusing code. Which, you will note, is one of the main problems that OOP is intended to solve.
Suppose, for example, that I maintain a package like ColorTypes but in a class-based language like Python or Java. Now further suppose that you want to treat each color object like a 3-vector (or however many channels it has) and do vector operations like addition, subtraction, and taking norms. You donât even insist on using operators like +
and -
, youâll happily write color1.add(color2)
, color1.sub(color2)
and color.norm()
. You try to convince me to add these methods to the Colorant
abstract base class. However, I donât want to add these methods: I donât want to take on more features to maintain and itâs not entirely clear to me if/how colors should behave like vectors because perceptual color space isnât linear in terms of various representations like RGB, CMYK, HSV, Lab, XYZ, etc.
Given my refusal to add methods to ColorTypes, what can you do? Traditional OOP would tell you to define your own subclasses. Problem solved! Right? Well, letâs think it through. The implementation is not as simple as it sounds. You want to add methods to Colorant
so that all the color types inherit the new methods you add and any generic operations you define work across all color types. So you subclass VectorColorant <: Colorant
. But now how do you get all the corresponding vector versions of specific concrete subtypes like RGB
, HSV
, CMYK
, etc.? You have to define your own parallel type hierarchy that mirrors the one in ColorTypes
. If your language supports multiple inheritance (Java does not, Python does), you can define VectorRGB <: (VectorColorant, RGB)
, but you have to do that for every possible subclass â even ones defined outside of ColorTypes
. This isnât great: the number of definitions you have to write scales with the number of concrete subtypes, but at least each definition is trivial.
If your language does not support multiple inheritance, then youâre pretty much stuck: you have to copy the definition of each subtype but inheriting from the Vector
-prefixed variant of what the original type inherited from. At this point, youâve copied the entire class hierarchy aside from the topmost abstract base class, which probably doesnât have much in it anyway, so you might as well just make your own copy of the whole thing into your own separate VectorColorTypes
package. You also fix a few annoyances with the original code while youâre at it. After a while the upstream ColorTypes
package makes a few changes that you havenât kept up with, and the ColorTypes
and VectorColorTypes
packages start to diverge. Theyâre still similar, but unless you both try really hard to keep them in sync, they gradually become more and more incompatible. OOPs!
Thatâs not even the extent of the problems. Suppose youâre lucky enough to be in a language that supports multiple inheritance and you created a mirror hierarchy of trivial Vector
-prefixed types without completely parting ways with the original ColorTypes
package. Now you encounter some really neat palette
method that someone wrote using ColorTypes
that takes a single RGB
value and produces an entire palette of harmonious colors based on it. But you want to use it with your VectorRGB
type. You canât use it directly, however, since the methods are defined specifically for the RGB
type. Even though VectorRGB
has the exact same structure as RGB
and even supports all the same methods that do all the same things (remember, I only wanted to add methods, not change the existing stuff), they are different types and not interchangeable: if some method returns RGB
values, you cannot apply methods for VectorRGB
objects to them. You have three options:
-
You can petition the author of the code to change it to handle
VectorRGB
as well asRGB
; they many not want to do this, since itâs an unnecessary complication. -
You can create a shim method for
VectorRGB
that converts theVectorRGB
value toRGB
, callspalette
on it and then converts the returned colors back toVectorRGB
. This works, but you have to do it for every function likepalette
. -
You can copy the code and duplicated it for the
VectorRGB
type and part ways with the original author.
None of these is that bad, but you have to do this for every bit of ColorTypes
functionality that you might want to use. Itâs also funny how OOP, which is supposed to be this great enabler of code reuse and sharing, keeps pushing us towards just making our own copy of someone elseâs code and modifying it .
The palette
method is actually quite simple, so adding support for VectorColorant
subtypes isnât that complicated. In more complex situations where color types are consumed or constructed, adding support for different subtypes leads to a proliferation of complex OOP patterns in real world code bases, like the visitor pattern, the abstract factory pattern, the factory method pattern, the prototype pattern, etc.
How does all of this work out in Julia with multiple dispatch? All you have to do is define methods like this in your own code:
add(c1::RGB, c2::RGB) = RGB(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b)
norm(c::RGB) = norm((c.r, c.g, c.b))
Thatâs it. You donât have to convince anyone of anything. You donât have to copy their code. You can add methods at whatever level of the abstraction hierarchy is appropriate. It doesnât even matter if other people want to use the same names for methods because if you define an add
function in your code and they define a different add
function in their code, these are separate function objects in different namespaces and thereâs no issue at all. In OOP, the namespace of methods is global and shared, so method name collisions are a problem. The key reason all this is better with multiple dispatch is that, unlike OOP where methods are defined inside of classes, methods belong to generic functions and are defined outside of the types they apply to â potentially not even in the same code base. Each user of a type can add methods as they please, which other users can reuse or ignore as they prefer.
So even if you never realized you encountered the expression problem because you didnât have a name for it, you almost certainly have. That time you wanted to just add one teensy tiny method to someone elseâs class but couldnât. That was the expression problem. That time you discovered that your favorite OOP languageâs ecosystem has two or more non-interoperable packages for some subject and you need some features from both of them but canât use them together. If not for the expression problem, they could share a core set of simple types while extending them with different methods on top of that shared core. That time you were tearing your hair out trying to understand what the SimpleBeanFactoryAwareAspectInstanceFactory
class is and how the heck youâre supposed to use it. That was also the expression problem: if people werenât forced to subclass just to add a few methods, they could just share concrete types instead of needing so many layers of abstraction. Despite seeming so different, these are all manifestations of the same core problem in OOP languages, a problem which multiple dispatch solves simply and elegantly, thereby eliminating all these issues at once. The effectiveness of this solution is borne out by the unusually high degree of code reuse and sharing we actually see in the Julia ecosystem.
Lisp is the original functional language and allows both mutation and rebinding of variables. Lisp and Julia are both functional languages, just not purely functional. The criterion for a functional programming language is good support for functions as first-class objects â you need to be able to pass them as arguments and return them from functions. That allows patterns like map
, filter
, etc. Some would also require support for lexical closures, which Julia also has.
This wikipedia page is a good illustration of how hard it is for a language to add multiple dispatch afterwards. I have to say Iâve seen some of those examples in real life.
What if Julia would officially support Multiple dispatch
way and OOP
way officially?
Both pros and cons would be solvedâŚ
(Although I found power of multiple dispatch
reading materials and easier to read write and collaborate but ⌠)
OOP is just single dispatch. Just dispatch on the first argument. Done.
What benefits does the OOP offer that multiple dispatch doesnât already solve?
OOP from my point of view means that you can add properties to classes and inherit them⌠But you can do this with Julia alreadyâŚ
I have nothing problem with it .
Iâve one silly problem that i am looking for , like in classes we use like class.
and hitting tab
gives all methods
interactively but in Multiple dispatch way.
Iâve to use a function which prints all methods at once and not interactive. As we have in julia,
methods(...)
I am thinking a way to get interactively those methods like auto completer
.
This is Is there Way to know methods/functions of classes(modules/functions) - #5 by vedasulo and I hope that alot of innovation have done, this would come in future .
Exactly this I was talking .
It seems these have been merged. I hope i will see this using some extensions in ideâs .
When could i see it in action?
a = [1]
giving a
all autocompletion interactively or tab . and it was added 12 days ago:) , I am waiting.
ThanksâŚ
No, itâs not merged yet â thatâs why the PR is listed as âopenâ. Even when it is merged, you wonât see it until Julia 1.7.
I hope will see soon.
Thank you @stevengj
Two words: Type Piracy.
Those are indeed two words.
While itâs often useful to be as terse as possible when making a point, I think using just âtwo wordsâ didnât convey what you wanted it to. Could you explain why you think those two words are relevant?
That example was not type piracy. It defined a new function which would be scoped to that module. For example SomeModule.somefunction()
and OtherModule.somefunction()
are two different functions which happen to share the same name, not type piracy.
Ah, just realised that the eBook is on sale for 5 Euro thanks for mentioning it!
Wonât it look basically the same in languages such as python? As I understand, the power of multiple dispatch comes not from just the ability to define new functions operating on existing types - this is possible in the majority of popular languages. In fact, your example doesnât need dispatch at all, if it doesnât do piracy:
def add(c1, c2):
return RGB(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b)
def norm(c):
return np.norm([c.r, c.g, c.b])