People seem to have a hard time grokking Julia’s approach to OO and understanding how it is possible to live without classical inheritance. I took a short break from screeching at them on Quora to write this guide about how how to do composition, encapsulation and polymorphism in Julia.
I also wasn’t shy about my opinions where I feel Julia could improve its support for object orientation. I have no background in CS and have been working as a professional developer for all of three years, so I figured I would be the perfect person to give people advice about software design.
Needless to say feedback and criticisms are welcome. I would like this guide to be as helpful, clear and accurate as possible, and I’m sure there are some mistakes in there. I would say I’m “in the advanced stages” of learning Julia, but I’m certainly no expert. plz help.
I’ve thought about this lately as I keep sliding back into a more OO like approach to things (within Julia). I think the human brain is just more well suited for OO programming, because it’s super simple and goal directed.
Like, “Let’s build a house”
OO programming: “We need to define the house class. We could have the room class inherit…”
Functional programming: “Let’s build a drill. Then we can build whatever we want.”
You have to think a lot more with the latter at first, but once you have everything built the only limit is your mastery of the tools at hand.
Anyway, in my quick read nothing stood out as “wrong”. It may or may not be worth noting on the last part about inheritance and composition that it’s really a trade off. You can spend the extra time writing out “inheritence” at a field level or write a bunch of extra boiler plate for OO inheritence (of many forms).
I think the level it’s explained at is very appropriate (especially traits).
Thanks for the feedback. I’ll think about how to mention the trade-offs between inheritance and composition.
I have a couple of thoughts about this. One is that “sliding into OO” in Julia strikes me as perfectly normal, after one gets the hang of executing OO patterns using the different feature set. Julia is, at a very fundamental level, an OO language, and OO patterns are all over the standard library. While Julia may not have classes, it is much closer to the likes of Python and Ruby than it is to Haskell.
Julia’s support for functional programming, such as it is, is enabled by the fact that functions are objects. As in most dynamic, OO languages, they provide some functional features implemented on top of “everything is an object” semantics.
While one does sometimes get a “functional feeling” when writing Julia code, it is very imperative and makes heavy use of side effects.
The other thing I often feel compelled to point out is that OOP and FP are orthogonal. Haskell is as purely functional as they come but it also has excellent support for composition, encapsulation and polymorphism through structs, type classes, algebraic data types and veeeeery fancy ways of describing generics. One can (and should) apply OOP patterns in large Haskell projects.
Functional programming, on the other hand, is primarily about the elimination of side effects. It would be entirely possible to have a pure FP language with classical inheritance, data hiding, etc. (though immutability makes data hiding a bit less important)
One can and should apply both strategies for abstraction in the battle against complexity in a large project.
OO is inherently unsuitable for scientific programming, which is what Julia trying to excel. OO mandates you to bind methods to object class, which is flipped logic compare to what one normally thinks about a mathematical operation.(i.e the spirit of the operation is general and for a specific data type, we follow this procedure)
Also, OO introduces internal states of an object, in scientific computing, we mostly care about data -> process -> output, and it’s a hassle to debug side-effects of some functions.
A good short letter to object OO is written by Erlang’s author.
A quick note regarding the intro: macros are normal julia functions internally. It’s just the way you normally invoke them (ie., as part of the macro expansion phase) that is somewhat opaque. To demonstrate:
julia> show_macro = getfield(Base, Symbol("@show"))
@show (macro with 1 method)
# 1 method for macro "@show":
 @show(__source__::LineNumberNode, __module__::Module, exs...) in Base at show.jl:552
julia> show_macro(LineNumberNode(1), Main, :(a))
println("a = ", repr(begin
#= show.jl:555 =#
value = $(Expr(:escape, :a))
I don’t think it is necessary to talk so much about OO and defend Julia being OO (in the first part). This is just terminology (some would argue that it is not OO, but that is pretty much irrelevant).
In the polymorphism section, I would not use “Holy Traits”, and then have to explain that the concept is not theological, but comes from a person’s name. I think that it is fine to just mention that Tim Holy suggested these, and just call it traits or trait-based dispatch.
Finally, while I respect your effort, I don’t quite see the need for another tutorial from this angle. If you think the manual could be improved, perhaps it would be better to contribute there. My concern is that a large fraction of these tutorials inevitably become unmaintained as Julia progresses and from the on they will just mislead users (as it happens nowadays with people finding and trying out pre-0.7 code).
I think that you and @jling are using different definitions of OOP. Of course there are multiple ones, and the issue is somewhat confused by the fact that a lot of languages wanted to jump on the OOP bandwagon in its heyday, but a common definition usually involves some sort of ownership of methods by classes (C++ is a canonical example).
In this sense, Julia, Common Lisp/CLOS, and Dylan are not designed for OOP as their primary paradigm. You may find this discussion interesting:
Thanks, I’ll try to reduce the irrelevant bits in the sections you mention.
I think the manual is quite good, in general–though it should perhaps give some direction on implementing traits, an area where I will consider contributing.
The way I see it, the point of tutorials like this is to present the information in a way that is relevant to software design with comparisons to features from other languages–focusing on patterns as opposed to features.
I think your concern about this kind of material becoming outdated and counter-productive is a valid one. My hope is that the language has more-or-less stabilized (not frozen) with the release of 1.0, and the content will become outdated at a more manageable pace. The inevitable need for change was also part of my motivation for hosting it on github and writing it in notebooks. If examples break, the notebook will make sure I know about it. If I forget about it, but someone else sees a problem, they can open an issue, make a PR or simply fork, if I’ve completely abandoned the document.
I realize using github and notebooks doens’t magically make this future-proof, but I think it’s definitely an improvement on a blog post, for example.
Methods in Julia are bound to types–they are simply bound to all the types of all their parameters rather than a single one. This small caveat aside, the standard library abounds with methods that are specifically related to the interface of a certain type. This is especially obvious with the methods that need to be implemented for various collection interfaces. Methods on Base.iterate, Base.setindex!, etc. are very much about binding behaviors to specific types, where additional arguments are typically specified in a more generic way (because a collection type really doesn’t need to care about the types of its members).
In short, multiple dispatch gives you everything methods on classes do–they just give you a lot more in addition to that. Using Julia methods to implement interfaces on types the way one does in classical OO is one of the most oft-repeated patterns in the Julia standard library as well as popular 3rd-party modules.
Cool. I should add that the exact calling convention for macros (as callable julia functions with additional LineNumberNode,Module arguments) has changed over time. I’m not sure whether it will change in the future, but the exact lowering here should probably be considered an implementation detail.
There’s one interesting factor in “classic” single dispatch OO languages which has huge practical consequences: the typical single dispatch notation obj.method(args) automatically gives a namespace in which to look up method in a way which doesn’t clash with obj2.method having the same method name for a different concept.
At first sight this is a pretty big convenience in allowing short concise names to coexist in different libraries.
On the other hand, the julia way of export + multiple dispatch makes name clashes more obvious and painful. I must admit that a few years ago I thought this was a design flaw of the export system and there should be a better alternative if only we could think of it. Now I’m not so sure; having these short term painful name clashes is a strong factor in encouraging the ecosystem to converge on shared meaning for verbs and that’s a great thing in the longer term.
You can of course always think about it this way, but I think this is pretty much an empty statement in Julia, as one can always add new types and methods for them. Talking about a function being “bound to” a set that is arbitrarily extensible is not very helpful.
I think that a method table is a much more useful concept here.
This discussion highlights why I think that thinking in terms of the concepts of the C++ -style OO approach hinders, rather than helps, learning idiomatic Julia. To experienced C++/Java/… programmers learning Julia, IMO the best advice is “forget all that”, for everyone else, “don’t bother about OO, because Julia isn’t.”
Part of it may be because my concept of OO is shaped much more by Smalltalk and Python than Java and C++. Julia and Smalltalk are much more similar to each other than either is to Java or C++. As a language Julia is objects and interfaces all the way down, i.e. Pure OO.
And then there is OOP, which, to my mind, is a set of design strategies for dealing with complex data. i.e. composition, encapsulation and polymorphism–strategies which can (and should) be applied in any language when dealing with complex data, and are even encouraged and facilitated in most functional programming languages.
The goal of my tutorial is to show Julian idioms and features for composition, encapsulation and polymorphism and how they are similar and different to the idioms found in other popular languages. To that end, I think it is useful to speak about object orientation because I think people who miss classes when coming to Julia really miss the power classes provide for creating abstractions on complexity. While Julia does have obvious differences from classical OO languages, the features it provides for data abstraction do lead to similar design patterns as are often observed in classical OO, as can be seen throughout the standard library.
In any case, I do appreciate you pointing out that it’s not really helpful to defend Julia as being a pure OO language (though I unequivocally believe it is). The focus of the tutorial is on strategies for data abstraction in Julia, and discussing the extent to which Julia conforms to various definitions of language paradigms is unnecessary and perhaps counter productive.
I see what you mean. I think about functional programming in terms of pure functions and putting constraints on side effects, as well as allowing a compositional approach to the description of processes through the use of first-class and high-order functions. I see OOP and FP as orthogonal in their concerns. However, if you think about that as conflicting approaches to how data and processes should be organized, I think I can see your point.
I’m probably just going to delete the line that says macros aren’t objects and leave it at that.
For me, class methods vs. multiple dispatch simply comes with a set of trade-offs. I think multiple dispatch is the more flexible and useful approach overall, but there are definitely advantages to classes for some things. (e.g. more namespaces)
I’m a big fan of namespaces and think the default way to leverage a library endorsed by the community should be with import and qualified names. Frequently-used objects should be imported explicitly with import MyModule: foo, bar on an ad-hoc basis. This is considered a best-practice in Python, and I think it makes a lot of sense for Julia as well. The only time I use using is for testing libraries interactively or very short scripts containing few names.
Anyway looser namespaces with Julia objects, from my perspective is neither a feature nor a flaw. It’s just a natural trade you make in exchange for the power and flexibility of multiple dispatch.
On the other hand, method lookup for objects specifically is not particularly difficult in Julia (using the methodswith function), it’s just not as amenable to tab-completion! It would be nice to see some creativity around how to integrate methodswith into development environments for code completion. (and maybe there already is some. I’m just using a basic editing environment for Julia with no completion of any kind, but I know Juno is very fancy. I tried to set up the language server with Emacs, but I haven’t succeeded, and I don’t find it so crucial, thanks to @Tamas_Papp’s Emacs REPL, Revise.jl, etc.)
Possibly only when you are defining new methods for them, otherwise using MyModule: foo would signal intent more clearly.
Personally, inside packages I always explicitly list what I need with using, and in scripts I just do using ThatPackage. I guess both have their place. Also, when I add methods, I almost never use import, but fully qualified names.
Good point. I need to review the differences between using and import again in the context importing objects explicitly. One of my few annoyances with Julia is that it has these two keywords for almost the same thing in that context, with some subtle differences. I wish they would have picked a lane with this prior to 1.0. Oh well.
Common Lisp’s approach to OO is almost identical to Julia’s, including the its lack of self-awareness, but it is still widely considered to be an object oriented language (among other things).
Edit: In any case, I’ve rewritten the introduction now to remove the argument that Julia is an OO language (which it is) because it doesn’t matter. The point of the tutorial is about Julian approaches to data abstraction, and I hope the new introduction reflects that better.