Multiple Dispatch vs Chained Object Method Calls

389 views
Skip to first unread message

Anton Andriyevskyy

unread,
Feb 6, 2014, 7:10:28 AM2/6/14
to juli...@googlegroups.com

Hello Julia community,

This is my first post and I believe it's related to the language developers rather than the language users - please forgive me if I'm mistaking and writing to a wrong list.

I'm PHP developer for 10+ years (yeap, a quite limited language).
Despite it solves all my needs, I strongly believe that dynamic languages should do much more than PHP does, so I've skimmed the whole Julia documentation with a pleasure.

A question has come up and is crossing my mind all the time since I've finished skimming, so I'm dying to hear comments from ones who develop or use the Julia language intensively.

As you have guessed already, I'm looking at Julia from the web applications point of view. I know the language's aim is not web, but it looks like the core is so powerful and natural that applying it for web might lead to great results, faster programming etc.

So, chained method calls.

A very great feature in JQuery library (very exemplary) and in any other popular programming language is a way to call multiple methods (I mean functions inside objects) in a chain:

obj.f1(a,b,c).f2(x.y).f3()


This is widely used in ORM libraries, for example:

$user = (new User)->setUsername('abc')->setAge(25)->save();

It looks like it is not possible with Julia, so what is the recommended way for concisely call multiple methods over one object and not have a deep stack of parenthesis like this:

f3(f2(f1(obj, a, b, c), x, y))

I cannot combine f1, f2 and f3 into a single function because there are tons of variations of "what calls what" and it always varies.


Thank you very much,

Regards,
Anton

John Myles White

unread,
Feb 6, 2014, 1:05:45 PM2/6/14
to juli...@googlegroups.com
Here's the current thinking on this topic: https://github.com/JuliaLang/julia/issues/5571

For now, you can do this by splitting things across lines. There's no need to do all the function calls on a single line.

In general, a Julia programmer would write code like

$user = (new User)->setUsername('abc')->setAge(25)->save()

in a way that looks more like:

user = User()
username!(user, "abc")
age!(user, 25)
save(user)

FWIW, I find that kind of style more readable than the chaining approach.

-- John

Ivar Nesje

unread,
Feb 6, 2014, 1:15:48 PM2/6/14
to juli...@googlegroups.com
Or just 
user = User(username="abc", age=25)
save(user)

Currently you have to define your own constructor to accept keyword arguments, and I think that is not going to change. There are apparently some issues with dispatch and efficiency.

Ivar

Kevin Squire

unread,
Feb 6, 2014, 2:12:34 PM2/6/14
to juli...@googlegroups.com
Just to add, chaining is possible right now, but it's a little ugly:

$user = (new User)->setUsername('abc')->setAge(25)->save();

would be

user = User() |> x->setUsername(x, 'abc') |> x->setAge(x, 25) |> save

Of course, as Ivar pointed out, you would never do this when constructing a type.

You didn't mention it, so I'm wondering if you've seen http://juliawebstack.org/?

As a user and contributor to Julia (but not a web developer), I'd say that the basic infrastructure for web programming is present (or easy to add), but because there aren't many users yet, you might find it a little rough around the edges.

Cheers,

   Kevin

Anton Andriyevskyy

unread,
Feb 6, 2014, 3:04:04 PM2/6/14
to juli...@googlegroups.com
User(username="abc", age=25)

- great, this works well for immutable data.

But it's often used inside conditions, for instance:

user = new User;

if (...)
  user
->setName(...)->setAge();
elseif
(...)
  user
->setFoo(...)->setBar()->isAdmin(true)
elseif
(...)
 
// and so on...

user
->save();

Kevin, thank you for linking to http://juliawebstack.org/ - no, I haven't seen it.
I'll be trying Julia on my next mini-project (I'm doing a lot) as soon as native MySQL/MariaDB support appears.

Offtipic. The only thing I should confess is counter-intuitive so far is the lack of single-dispatch.

While multiple dispatch works great when there is no method ownership preference between its arguments, it still looks quite strange that one cannot define very "my own" methods which make sense only for particular type, thus allowing to encapsulate raw data fields when needed.

Leah Hanson

unread,
Feb 6, 2014, 3:19:27 PM2/6/14
to juli...@googlegroups.com
Could you give an example of the behavior you're looking for in "my own" methods?

-- Leah

Spencer Russell

unread,
Feb 6, 2014, 3:28:08 PM2/6/14
to juli...@googlegroups.com
Hi Anton,

If I understand you correctly, Julia supports the sort of single dispatch you're used to, the syntax is just a little different.

type Foo
   bar
   baz
end

function fuzzify(f::Foo)
    f.bar = rand()
    f.baz = rand()
end

function unfuzzify(f::Foo, newbar, newbaz)
    f.bar = newbar
    f.baz = newbaz
end

f = Foo(0, 0)
fuzzify(f)
unfuzzify(f, 2, 3)

So the fuzzify function will only get dispatched if the first argument is a type Foo.

Anton Andriyevskyy

unread,
Feb 6, 2014, 3:34:17 PM2/6/14
to juli...@googlegroups.com
Probably a synthetic example like this:

cat.miauw()

There is no need for multiple dispatch for the miauw() method as no other animal can do it.

Jokes aside, there are cases when internal data is not recommended to touch separately and thus should be only set by public methods to keep the state of object consistent.

To demonstrate something more complex, let it be some equivalent of Regex which is dynamically composed under flow of if/elself conditions and matches, and it is required to always be in working state, starting from an empty state, i.e.
//
or
^.*$

Internal structures may represent that Regex as a try and only add_expr()-like methods could be exposed. Not only those methods are very specific for my Regex-analogy, but also I would not allow to touch all the internal data.

A second example would be a web application like WordPress where users can install plugins and themes. It's very insecure to have even theoretical possibility for 3rd party code to touch the core web application engine. Of course I'd declare public API and make it as functional as possible in the sense that there is no state to change and affect and it all flows in one turn. But there is still risk as soon as I introduce state AND as soon as I pass structures to be changed by plugin. For instance I could pass Menu object to plugins and let them add/remove items. Thus passing a Menu object by reference which only few public methods available would do.

Hope it makes sense.

Spencer Russell

unread,
Feb 6, 2014, 4:15:17 PM2/6/14
to juli...@googlegroups.com
For the cat example, there's no difference conceptually between cat.miauw() and miauw(cat), it's just different syntax for the same thing. If miauw is defined "function miauw(c::Cat)" then it "belongs" to the Cat type. I think one of the things that may feel a little strange at first is that in a lot of OOP languages like C++, python, etc., you declare a class as a block and you declare methods on the class within that block. In Julia you define the type as a block, and then define the methods on that type afterwards. While the latter may feel a little strange if you're used to the former, they end up doing the same thing (I think, someone please correct me if I'm overlooking something).

Your 2nd point is regarding data encapsulation. In general Julia doesn't have the concept of private variables in the same way that C++ does. As far as I know it follows a more pythonic approach where all type members are externally accessible. Some people may (by convention) prepend variables that aren't supposed to be touched with an underscore, ("_myvar"), but I'm not sure if that's considered idiomatic or not.

For your wordpress example, In the case where you're allowing untrusted code to run inside your application, language-provided encapsulation won't save you if you're really worried about security. If it's more about the possibility that well-intentioned code may modify data that you as an API designer don't want it to (which I think is the normal use-case for OOP encapsulation), that's where convention and documentation come in, and I believe that it's one of those language design choices that competent and intelligent people can disagree on (though I happen to like the simpler and more permissive approach that Julia and Python take).

Hopefully this was clarifying.

-s
-s

Anton Andriyevskyy

unread,
Feb 7, 2014, 7:44:03 AM2/7/14
to juli...@googlegroups.com
well-intentioned code may modify data that you as an API designer don't want it to
- that's where convention and documentation come in

The problem with conventions and documentation is that you have to spend time to learn it and get used to following it. If you had no way to change internal core fields and only some public methods were accessible, it would turn out that it's an intuitive API and you wouldn't even need to read that documentation and learn conventions to make a small plugin for a quick hack / fix / customization. In my opinion it's vital for the development speed.

Multiple dispatch is great, but why does it exclude single dispatch? - that I do not understand. Julia tries to combine the best practices from all the languages, so if that helps in a couple of scenarios, why not to make it another option available.

Also, my own observation:

This sentence reads well: "my cat is miauwing".
You don't write "miauwing my cat".

The same, (a) cat.miauw() reads better than (b) miauw(cat).
The (b) syntax hints there should be something else that I can pass to the miauw() function, so it is counter-intuitive when you read the code until you see at the miauw() definition.

I don't argue about a + b where it is a function over two independend arguments with similar "meaning weight".

IDEs and autocompletion

It's obvious that not only the goodness of the language itself is a key to be spread and be used, but also the tooling. That's why Scala team has been investing a lot of effort into developing tools for those who uses the language - IDE, knowledge bases, tutorials, quick-start tools, deployment etc.

So why I'm talking about tools? One thing is important when working with medium and huge projects - autocompletion.

When I'm working with a cat object, it's in the context in my head, I know it's name and it's easy to start a line of code with:

cat.| - where | is a cursor

At that point IDE will show a dropdown with all the methods available for cat.
I'll immediately see miauw() in the list, at least after typing "m".

Now back to Julia, if I want to do something with a cat but I don't remember what that method's name is, what should I do? Even if I type "m", I'll see all unrelated methods starting with "m" (I mean, not related to the cat).

This makes the process of writing code harder. And the bigger your project is, the harder it becomes to keep everything in context in your head.

So the language design choice is perfect, but my 2 cents after writing many years for web - I don't feel comfortable with struggling with method names.

Back to our WordPress example, there will be 1k+ methods overall.
So it looks like writing Menu.appendItem() both reads well and easy to type, i.e once you type "Menu." you can see what you could do with it. Makes sense?

Tobi

unread,
Feb 7, 2014, 8:04:13 AM2/7/14
to juli...@googlegroups.com


Am Freitag, 7. Februar 2014 13:44:03 UTC+1 schrieb Anton Andriyevskyy:
IDEs and autocompletion


At that point IDE will show a dropdown with all the methods available for cat.
I'll immediately see miauw() in the list, at least after typing "m".

Now back to Julia, if I want to do something with a cat but I don't remember what that method's name is, what should I do? Even if I type "m", I'll see all unrelated methods starting with "m" (I mean, not related to the cat).

I plan to implement that in the following way:
You write "cat." and the autocompleter will look for all methods that start with the Cat type. Once you have selected "miauw" it will rewrite it to "miauw(cat,"

But getting autocompletion in Julia is still not very easy. When using duck typing one is kind of lost.

Kevin Squire

unread,
Feb 7, 2014, 8:10:02 AM2/7/14
to juli...@googlegroups.com
This sentence reads well: "my cat is miauwing".
You don't write "miauwing my cat".

Totally off topic, but actually, in some languages you do (Hebrew and Irish, for example, according to http://en.wikipedia.org/wiki/Subject%E2%80%93verb%E2%80%93object).  

(Someone who speaks those languages can correct me, of course.)

Anyway, regarding your other comments: Julia has a particular way of doing things that is meant to be efficient and performant.  I don't agree, either, with all of the choices that have been made.  But I understand that there are both performance tradeoffs and other people's preferences which decide those things, and I'm willing to be flexible enough to go along with it.  And generally, I've gotten used to it, and rather enjoy using it.

One minor point: technically single and multiple dispatch are independent of notation.  As you pointed out, in other languages, single dispatch (if available) is frequently done using dot notation (e.g., cat.miauw()), and in Julia, single dispatch is done with miauw(cat).   

So what you seem to be missing isn't single dispatch, it's dot notation for single dispatch.

Cheers!

   Kevin

Ivar Nesje

unread,
Feb 7, 2014, 8:17:58 AM2/7/14
to juli...@googlegroups.com
I think your opinions is interesting, but I am not sure that I agree. When it comes to the value of convention, documentation and intuitiveness, I think there is huge value in a small language. Introducing overlapping concepts (eg. single and multiple dispatch) has never made anything more intuitive and easier to learn. There might be value in adding some sort of protected / private qualifiers, but most languages actually provide an API for accessing private variables through reflection, or other sneaky means.

If you want to have the conditional initialization, you can create a update function with keyword arguments for all the fields:
user = User();
if (...)
  update!(user, name="...", age = 42)

elseif 
(...)
  update!(user, foo="", bar="", isAdmin=True)

elseif 
(...)
  
// and so on...

save!(user)
 
As Tobi says, the duck typing in Julia makes autocompletion hard, but it is easy to not use duck typing in Julia, so it should be possible to do something.

Pierre-Yves Gérardy

unread,
Feb 7, 2014, 9:09:29 AM2/7/14
to juli...@googlegroups.com
You could get this kind of behavior right now using a macro. It can be compiled to simple function calls rather than chaining anonymous functions.

The implementation is left as an exercise to the reader.

    @chain :(transmogrify(5,6)->buzz("whooopsie"))

becomes

    buzz(transmogrify(5,6), "whoopsie)

You have to quote the expression. It is then turned into a "->" Expr. Without quoting, the parser rejects it.

In the same vein you could have @tap (or @with?)

    @tap :(foo ->bar()->baz())

becomes

    (bar(foo);baz(foo);foo)

Tom Short

unread,
Feb 7, 2014, 11:07:42 AM2/7/14
to julia-dev
You could do away with the quoting if you use |> as:

@chain transmogrify(5,6) |> buzz("whooopsie")

Cristóvão Duarte Sousa

unread,
Feb 7, 2014, 11:07:56 AM2/7/14
to juli...@googlegroups.com
I had firstly posted this answer to https://github.com/JuliaLang/julia/issues/5571#issuecomment-34431957 in the same issue.
Since it was OT there, I'm moving it here.

-------

I wouldn't say that in other languages, for example in C++, an object really "contains" its methods, if we think in terms of each method data. Those methods are just functions bounded to be used only over the associated objects, with the syntax sugar of the dot  operator.

But in Julia it is the same, `f(x::MyType)` can only be used with `MyType` objects too. The only difference is the calling syntax (which this issue is somehow about) beeing more close to C.

The underlying implementation is pretty the same (at least in Julia, Python, and C++, which I know): object methods are called with a reference to an instantiated object as the first argument.

When I first started learning Julia I had missed the the classical `obj.method` approach but I quickly started to really enjoy the new way. One of the reasons is that there is only one right way of doing things; e.g., we know that it has always to be `size(A)` and not `A.size()`.

(Anyway, I agree that in languages where there is a distinction between public and private data exists the method/non-method function distinction makes sense.)

Pierre-Yves Gérardy

unread,
Feb 7, 2014, 4:36:54 PM2/7/14
to juli...@googlegroups.com
Actually , -> works fine, I got bitten by the implicit return in my test macro.

Spencer Russell

unread,
Feb 7, 2014, 5:06:18 PM2/7/14
to juli...@googlegroups.com
In general, for anyone interested in various function chaining approaches, see https://github.com/JuliaLang/julia/issues/5571, where it's been discussed at some length.

-s

Steven G. Johnson

unread,
Feb 7, 2014, 5:45:33 PM2/7/14
to juli...@googlegroups.com


On Friday, February 7, 2014 7:44:03 AM UTC-5, Anton Andriyevskyy wrote:
Multiple dispatch is great, but why does it exclude single dispatch? - that I do not understand. Julia tries to combine the best practices from all the languages, so if that helps in a couple of scenarios, why not to make it another option available.

It doesn't exclude single dispatch; this is confusing spelling with function.

One could certainly implement tab-completion such that typing "foo,<tab>" comes up with a list of methods that operate on typeof(foo) as the first argument, and then replaces "foo," with "method(foo,".  So I don't think you need to lose functionality there.

[Indeed this would be a nice feature to implement in the REPL, and in IJulia once IPython updates its tab-completion semantics according to some discussions we've been having.]

Regarding data encapsulation, that is certainly an argument for using accessor methods property(x) and property!(x, newvalue), and again this is just a matter of spelling.

All this being said, however, the ability to overload the "." operator (in which case you could write accessor methods with the dot syntax) is certainly going to happen at some point soon, if only to make it easier to provide Julia wrappers for languages such as Python that use this syntax.  (https://github.com/JuliaLang/julia/issues/1974)

--SGJ

Mike Innes

unread,
Feb 8, 2014, 11:17:45 AM2/8/14
to juli...@googlegroups.com
There's actually a macro in Lazy.jl which solves exactly this problem (it's not in Base, though, unfortunately). For example:

using Lazy

@> obj f1(a,b,cf2(x.yf3()

Hope this helps.

Tom Short

unread,
Feb 8, 2014, 9:19:03 PM2/8/14
to julia-dev
Hadn't seen that. Lazy looks awesome.

Spencer Russell

unread,
Feb 8, 2014, 9:47:22 PM2/8/14
to juli...@googlegroups.com
wowee. That's awesome.

-s
Reply all
Reply to author
Forward
0 new messages