Discussion: Multiple dispatch vs Single Dispatch syntax.

966 views
Skip to first unread message

Vinuth Madinur

unread,
Jul 12, 2015, 9:54:46 AM7/12/15
to juli...@googlegroups.com
I have read through some of the past discussions about how multiple dispatch is incompatible with the OOP syntax for single dispatch function call. And I have also gone through the discussion about allowing overriding of call() and fears about it's misuse. 

I'm sure this topic has been discussed enough times and at length. I had a proposal to discuss and if this has already been discussed, kindly point me to it. I do not mean to distract from all the other more pressing matters. I'm sorry if I'm doing that.

I have a proposal to maintain the ideological purity of the multiple dispatch model in the syntax as well as give the more familiar, readable and friendly syntax from OOP. Multiple dispatch basically means co-ownership / co-belonging of a function to multiple objects. In which case, can't we just call the function on a tuple of these objects? If I'm not wrong, that is already the case. The dispatch happens on the tuple of arguments. But what if we flip it and divide the "owners" from the "arguments"? (essentially what the semi-colon (;) inside the function signature does currently) And as a special case, if only a single object owns a function it can be simply called on that object.


For example:

Instead of +(1,2,3)
(1, 2, 3).+()  (There is anyway a special syntax accommodation for this as 1 + 2 + 3)

(x, y).plot() instead of plot(x, y)

cat.meow() instead of meow(cat)

(r"^\s*(?:#|$)", "not a comment").match()


Currently, it's hard to properly design the method signature to reflect the correct meaning. For example:
search("Hello World", "e") -> Does search belong to both "Hello World" and "e"? Does "Hello World".search("e") feel more natural and is a better design?

Are all "required" parameters "owners" of a method always? 
Should all keyword arguments have to have a default value?
Should all variable arguments become "owners" of a method?
If an argument has a default value, it can be skipped during the method call. In how many cases can we claim the method "belongs" to this argument?
map(x) do ... end, does map "belong" to this function?


The above proposal would make method design more intuitive and clearer. It'll also preserve and convey the meaning of multiple dispatch while retaining the readable syntax for single dispatch.


Thoughts?

John Myles White

unread,
Jul 12, 2015, 11:22:07 AM7/12/15
to juli...@googlegroups.com
This is a cute idea. But I see some problems with your proposal.

(1) I'm troubled by the distinction you've drawn between single argument functions and multiple argument functions. If (a, b).f() is equivalent to f(a, b), then any Julia developer would assume that f(a) should be equivalent to (a, ).f(). Breaking that assumption means that you've effectively created a new kind of language construct that has syntax that has surface similarities to the existing tuple syntax, but isn't fully equivalent.

(2) If we allow f(a, b) and (a, b).f(), I don't see any way to prevent the gradual formation of mutually incomprehensible dialects of the language. We already give people enough syntactic sugar that they've started to abuse it. I'd be very hesitant to offer more.

Taken collectively, I think the problems this change would create can't be compensated for by the gains it would provide.

 -- John

Stefan Karpinski

unread,
Jul 12, 2015, 3:21:34 PM7/12/15
to juli...@googlegroups.com
The real question is where you look for function names. When you write f(a,b), a and b are not involved in resolving the name "f" whereas in single dispatch languages when you write a.f(b) you look for "f" inside of a – in its class typically, which doubles as a namespace. That is the non-superficial reason that class-based single-dispatch can be quite nice. As far as I can tell, this proposal does not give you that benefit – it's just an alternate syntax for what we already do.

Vinuth Madinur

unread,
Jul 13, 2015, 1:31:52 AM7/13/15
to juli...@googlegroups.com
Yes. It's an alternate syntax for what is already there. The actual method is looked up based on the types of the arguments, which can be said to "belong-to" this collective. 

This will allow chaining (even on multiple return values) and more correctly depicts the concept of multiple dispatch. Currently there is no separation between "belonging" and "just an argument". Arguments should be separate from the types to which the function belongs. Also currently, keywords arguments have to have default values and the arguments in the regular list cannot be passed using keywords (like in python). So it's hard to design function interfaces in a consistent manner. 

John's points:

1. function f1(a::Int) ... end

would be different from:

function f2(; a::Int) ... end

a.f1() would be valid, whereas a.f2() wouldn't be. In second case, a is just an argument. Inversely, f1(a) also wouldn't work. I'm not talking about this syntax as a syntactic sugar. I'm talking about it as "the" syntax.


2. I'm saying f(a, b) shouldn't always be equivalent to (a, b).f();


So, if multiple types are used in looking up a method, then the method 'belongs-to' this collective. Which is already the case..

1. This syntax would allow "just an argument" to not become part of the lookup process
2. Have a more flexible keyword based argument passing
3. Allow chaining, even on multiple argument returns.
4. Give the OOP based more readable single method dispatch when the method belongs to only a single type.
5. Retain and better communicate the concept of multiple dispatch.
6. etc.,

Vinuth Madinur

unread,
Jul 13, 2015, 4:20:24 AM7/13/15
to juli...@googlegroups.com
There are two parts to this: function name resolution and method lookup.

I'm saying function names can remain as it is and continue to be resolved according to their namespace [I haven't entirely thought this through :) ]. But method lookup can be based on the tuple used to invoke the function. And removing "arguments" from this lookup process.

Isaiah Norton

unread,
Jul 13, 2015, 9:47:06 AM7/13/15
to juli...@googlegroups.com
Yes. It's an alternate syntax for what is already there.
 Inversely, f1(a) also wouldn't work. I'm not talking about this syntax as a syntactic sugar. I'm talking about it as "the" syntax.

These two statements are self-contradictory.

1. This syntax would allow "just an argument" to not become part of the lookup process

This doesn't make sense. Argument types (specified or inferred) are always part of the lookup process. That's kind of the point of multiple dispatch.

Anyway, as usual, the proof is in the pudding: Julia has powerful metaprogramming capabilities which would allow you to prototype this syntax as a macro. Go for it.

Vinuth Madinur

unread,
Jul 13, 2015, 9:48:38 AM7/13/15
to juli...@googlegroups.com
Maybe "dot" is a bad choice of operator in my above examples. Because "dot" currently means namespace resolution. But syntactic namespace is different from looking up the exact method that implements an interface.

(a,b,c).f()  -> here I don't mean f is within (a, b, c). But I'm looking for a method in f that implements for (a, b, c). So some other operator might be apt here than the "dot". 

But setting that aside, does this make sense? 

Vinuth Madinur

unread,
Jul 13, 2015, 9:57:29 AM7/13/15
to juli...@googlegroups.com
Currently, keyword arguments are not part of the lookup process, much like arguments in python methods. What I mean is having all arguments behave like keyword arguments, i.e., provide the arguments necessary for an implementation, but not make them be part of deciding what implementation to use.


Currently all non-keyword arguments participate in the lookup process. But conceptually all arguments are not made equal. Only few of these arguments decide the implementation to use. The method can be said to "belong-to" only this tuple of arguments, not all arguments.



Isaiah Norton

unread,
Jul 13, 2015, 10:05:41 AM7/13/15
to juli...@googlegroups.com
I see. So 

(a,b).f()

would be the only syntax for multiple dispatch?

(a,b).f(x)

would be syntax for multiple dispatch with x as a keyword argument?

Come on. This is a comically breaking change. Try using Julia *as it was designed* for a while, or find another language that better suits your aesthetics.

Tom Breloff

unread,
Jul 13, 2015, 10:07:53 AM7/13/15
to juli...@googlegroups.com
The more I read this thread, the more I hate this idea.  One of the greatest things about julia is the lack of ownership... anything that tries to shove ownership into the syntax is just hampering one of the greatest features.  Consider a simple "set" method, and the following concepts:
1) "Set the table with the knives and forks."
2) "Set the spoon on the table."
3) "Set the dish in front of Joe's seat."

In OOP, the verb set "belongs" to the nouns.  Reading #1, you might think the verb set should belong to the type Table, so that you call
table.set(knives, forks)

When you see #2, you'll be forced to reorder your thoughts a little:
table.set(spoon)

Then when you get to #3, you realize the mess you're in:
joe.getSeat().getTable().set(dish)

And for that last one to work, you need to have defined all possible relationships between objects ahead of time so that you can get the object that owns the verb.

Julia changes how you think... you can now think in terms of verbs, and simply give some rules as to what those verbs mean in context.  The same concepts in Julia:
set(table, knives, forks)
set(spoon, table)
set(dish, joe)

As long as you supply some contextual rules, the verbs are completely separate from the nouns, and it becomes easier and more natural to reason about code.  Sure you can accomplish the same thing, but the OOP mindset is limiting and unnatural.  It just happens to be a mindset that developers learned...

Vinuth Madinur

unread,
Jul 13, 2015, 10:14:11 AM7/13/15
to juli...@googlegroups.com
What I'm saying doesn't preclude the above use case. There can still be functions with no ownerships. But to say that ownerships shouldn't exist at all, might not be a valid stance to take.

With this discussion, I'm just trying to explore a possibility that hasn't been discussed before and check it's advantages / disadvantages / limits etc.,


Vinuth Madinur

unread,
Jul 13, 2015, 10:27:08 AM7/13/15
to juli...@googlegroups.com
The way I see "ownerships", "belongs-to", "dispatch", is that this decides the exact "implementation" of a function. In OOP only a single object decides the implementation to select. In multiple dispatch, multiple objects decide the exact implementation of a function. 

Currently, these objects are the same as arguments to that function. Which it need not be. Function arguments should be more about providing data required for an implementation.

Tom Breloff

unread,
Jul 13, 2015, 11:03:10 AM7/13/15
to juli...@googlegroups.com
The syntax re-ordering you propose, in my mind, is asking that we all speak like Yoda.  "This we should plot" instead of "Plot this".  "This value we should set" instead of "Set this value".  To quote: "You must unlearn what you have learned"...

Stefan Karpinski

unread,
Jul 13, 2015, 11:12:42 AM7/13/15
to juli...@googlegroups.com
I think the phrase "comically breaking" sums it up. I also don't really see what benefit such a change would bring – it moves from a familiar and intuitive function application syntax to an unfamiliar and confusing syntax – without adding any expressive power. We already program Julia in something fairly close to the way we'd like to. As a result, while there may be some deep changes in the future, those changes tend not to look that much different on the surface, but rather serve to generalize and extend the way the language already works. Call overloading was a good example – it's a very powerful feature that fundamentally changes how the language works but most code looks and behaves exactly the same.

As Isaiah said, you can write a package that implements all of this with macros and see how people like it. If it's really compelling people will use it.

Vinuth Madinur

unread,
Jul 13, 2015, 1:35:58 PM7/13/15
to juli...@googlegroups.com
Oh well :)

Yeah, I agree it confuses more than simplifying things. Thanks for indulging me nonetheless.

Stefan Karpinski

unread,
Jul 13, 2015, 1:59:17 PM7/13/15
to juli...@googlegroups.com
No worries. It's definitely a worthwhile observation that multiple dispatch can be viewed as external single dispatch on anonymous tuple types. But it's not clear that basing syntax on this observation makes things clearer / easier.

Scott Jones

unread,
Jul 13, 2015, 5:58:52 PM7/13/15
to juli...@googlegroups.com
Yep, I agree with Stefan here, it's definitely an interesting idea, and way of thinking about multiple dispatch vs. OO programming, but I think Julia is fine the way it is, and if you really want to program that way, Julia is so darn flexible with metaprogramming that you could probably handle that with macros.

Stefan Karpinski

unread,
Jul 13, 2015, 6:38:56 PM7/13/15
to juli...@googlegroups.com
I would argue that one of the beauties of multiple dispatch is that you don't have to think about dispatch when you call a function – it's just a function that takes values as arguments. The fact that the function may be defined in pieces is merely an implementation detail – you could just as well have implemented the same thing with run-time type checks. Expressing the behavior with dispatch just happens to be more convenient, extensible, and something the compiler can reason about.

Isaiah Norton

unread,
Jul 14, 2015, 10:02:40 AM7/14/15
to juli...@googlegroups.com
My apologies for the harsh response yesterday. I don't mean to dismiss the idea in general -- this could be an interesting thing to try in an experimental language. But in the context of Julia it is essentially proposing to create a different language.

There are some interesting platforms for "rapid language development" such as Truffle and PyPy that might be of interest for experimenting with such a design, though prototyping as a Julia macro would be a much easier way to start.

Scott Jones

unread,
Jul 14, 2015, 11:44:26 AM7/14/15
to juli...@googlegroups.com
Couldn't you define a macro that gives you most of this, at the cost of a few extra characters?
(I'm not sure, I still haven't done any real fancy meta-programming in Julia)

Something like:
@d(dispargs, f(otherargs))
instead of:
dispargs.f(otherargs)

where dispargs is a single value or a tuple.

Josh Langsfeld

unread,
Jul 14, 2015, 5:37:36 PM7/14/15
to juli...@googlegroups.com
It was very easy because the dot operator on the tuple already parses as valid syntax. Here's a quick prototype:

macro invertedcall(call_expr)
   
@assert call_expr.head == :call
   
@assert call_expr.args[1].head == :(.)
    invargs
= call_expr.args[1].args[1]
    methodsym
= call_expr.args[1].args[2].args[1]
    normalargs
= call_expr.args[2:end]

    output
= Expr(:call, methodsym)
   
#Deal with keyword arguments in the normal argument list
   
if length(normalargs) >= 1 && isa(normalargs[1],Expr) &&
            normalargs
[1].head == :parameters
        push
!(output.args,normalargs[1])
        normalargs
= normalargs[2:end]
   
end

   
if isa(invargs,Expr) && invargs.head == :tuple
        append
!(output.args, invargs.args)
   
else
        push
!(output.args, invargs)
   
end
    append
!(output.args, normalargs)
    println
("Rewritten to: ", output)
    esc
(output)
end


Some calling examples:

julia> X = rand(1:10,3,3)
3x3 Array{Int64,2}:
 
1  9  6
 
3  6  1
 
9  5  4

julia
> @invertedcall X.sort(1)
Rewritten to: sort(X,1)
3x3 Array{Int64,2}:
 
1  5  1
 
3  6  4
 
9  9  6

julia
> @invertedcall X.sort(1,rev=true)
Rewritten to: sort(X,1,rev=true)
3x3 Array{Int64,2}:
 
9  9  6
 
3  6  4
 
1  5  1

julia
> @invertedcall (X,2).sort()
Rewritten to: sort(X,2)
3x3 Array{Int64,2}:
 
1  6  9
 
1  3  6
 
4  5  9

Getting this form to do indefinite chaining would be a bit more of a headache...

Scott Jones

unread,
Jul 14, 2015, 5:42:08 PM7/14/15
to juli...@googlegroups.com
Julia wins again!

On Tuesday, July 14, 2015 at 5:37:36 PM UTC-4, Josh Langsfeld wrote:
It was very easy because the dot operator on the tuple already parses as valid syntax. Here's a quick prototype.
 

Vinuth Madinur

unread,
Jul 23, 2015, 8:34:11 PM7/23/15
to julia-dev, scott.pa...@gmail.com
After having thought about this some more and reading past discussions, I believe there is a way to leverage the strength of multiple dispatch to implement a full fledged OOP style system, without any modifications (unlike my earlier proposal).

I have put these thoughts as code in the attached file. Using this, one can attach correctly namespaced, arbitrary properties and methods to: any Type (abstract or concrete), any instance, a collection of Types or instances, etc., And due to the nature of multiple dispatch, a property defined on an abstract Type, will be available on it's concrete types as expected.

Please excuse the OOP idioms used and the use of .. operator.


Thanks,
Vinuth.
julia_dispatch.txt

Stefan Karpinski

unread,
Jul 27, 2015, 6:02:01 PM7/27/15
to juli...@googlegroups.com
Question: can one dispatch on the items to the right hand side of the verb? I.e. in (cat, dog)..play(toy1, toy2) does which method of play gets called depend on toy1 and toy2 as well as cat and dog?

If so (which seems to be the case from the examples given), then this seems like it causes an pointless explosion in the number of equivalent ways to write the exact same thing. Do these all mean the same thing?:
  • play(cat, dog, toy1, toy2)
  • ().play(cat, dog, toy1, toy2)
  • cat.play(dog, toy1, toy2)
  • (cat).play(dog, toy1, toy2)
  • (cat, dog).play(toy1, toy2)
  • (cat, dog, toy1).play(toy2)
  • (cat, dog, toy1, toy2).play()
Why have so many ways to express the exact same function call?

If not, then this seems like an unfortunate coupling of how a function is defined with how it is used: the caller is forced to be aware of which arguments are used for dispatch and which aren't. Worse still, if the implementor decides at some later point that they want to dispatch thing to the right of the verb, they can't do so without breaking user code.

Vinuth Madinur

unread,
Jul 28, 2015, 4:09:08 AM7/28/15
to juli...@googlegroups.com
Dispatch on function "play" actually happens only on the right hand side of the verb.

The function play() is namespaced. It can be accessed via any tuple of (cat, dog) objects and is not a global object. It's namespaced within any (cat, dog) instance.

So, all the above methods are not equivalent. The function play is dispatched only on it's arguments that have been explicitly defined on it. The values of c::Cat & d::Dog are available to it via context (currently added as keyword arguments) like it would if "play" was an instance method. So, alternatively play can be called as play(toy1, toy2; c = Cat(), d=Dog())

But to get a reference to the function "play" an instance of (cat, dog) is required. 

There are two dispatches: 
1. On (cat, dog, Val{:play}) to get the function play.
2. On (toy1, toy2) to execute function play.

First dispatch is used for namespacing and attaching arbitrary fields and methods to Types and instances. Second dispatch is the actual execution.


Vinuth Madinur

unread,
Jul 28, 2015, 4:14:53 AM7/28/15
to juli...@googlegroups.com
In my example,  the function play has 2 methods:

1. play(toys::Int...)
2. play(toys::ASCIIString...)

and it is dispatched only on these.

Stefan Karpinski

unread,
Jul 28, 2015, 10:22:23 AM7/28/15
to juli...@googlegroups.com
So there are two levels of dispatch:

1. Find which "function" to call by dispatching on the objects to the left of the verb.
2. Call that function on multiple arguments, which are also dynamically dispatched on.

Seems complicated. What's the advantage of two levels of dispatch?

Vinuth Madinur

unread,
Jul 28, 2015, 10:58:25 AM7/28/15
to julia-dev, ste...@karpinski.org
This allows anyone to dynamically associate metadata / functions with any object / Type in a way that the Type hierarchy is respected.

For example, I can associate a new attribute "age" on the abstract class Animal and access it from Cat / Dog instances. One can even associate arbitrary data fields to function objects, etc., 

And because it uses multiple dispatch to do this, these fields / functions can be added to single objects or on a tuple of objects / types. These new fields / functions wont pollute the global namespace. The functions will have access to the objects to which they are attached.

This partially uses the suggestion made by Jeff for overloading the dot operator and uses multiple dispatch to implement a namespaced dynamic OOP syntax. Makes chaining easy, potentially more readable and makes it easy to implement other concepts like Traits, etc.,

Tom Breloff

unread,
Jul 28, 2015, 11:22:49 AM7/28/15
to juli...@googlegroups.com
I'm with Stefan... I don't see how this has added anything to the language, other than giving it a OOP feel.  If I understand you, you see a few benefits:

- New methods do not pollute the global namespace.  Solution? Use modules.
- Easy chaining.  I agree it can frequently be useful to chain, but the ability to chain means that everything must be an object, and every method must return "self".  If you want something better than chaining, try currying, or use macros.
- More readable.  I disagree in all but the most basic examples... even then I'll usually disagree.
- Easy to implement traits.  One has nothing to do with the other, since you're talking about defining methods that are still equivalent to multiple dispatch, just not in global scope.

All in all, I think you've simply spent too much time in an OOP mindset and you're having a hard time letting go.  Multiple dispatch is a superset of object-owned functions... the syntax you're proposing adds complexity while removing functionality... not a very good trade-off in my mind.

Vinuth Madinur

unread,
Jul 28, 2015, 11:46:20 AM7/28/15
to julia-dev, t...@breloff.com
Naah. I saw that "dot overloading", "chaining", "traits", "associating metadata" etc., are all still open issues on the issue tracker and trying to contribute ideas / thoughts. While I do come from OOP mindset, I'm not hell bent / desperate to make Julia one. I continue to use it as is on a few of my projects. 

I do feel some amount of flexible dynamic nature to julia objects than the simple structs can add value. 

This exercise helps me understand language principles better and perhaps add to the discussion archives of bad ideas in julia for people itching for oop concepts like me :)

"the syntax you're proposing adds complexity while removing functionality"
I'm not proposing making any syntax changes, just new ways to compose data. So no question of functionality getting reduced. It's just a macro based system.

Stefan Karpinski

unread,
Jul 28, 2015, 12:03:27 PM7/28/15
to juli...@googlegroups.com, t...@breloff.com
It's an interesting idea and worth exploring, but it does feel more complex and less intuitive than the simple "apply functions to values" system that we have now.

Vinuth Madinur

unread,
Jul 28, 2015, 12:49:54 PM7/28/15
to juli...@googlegroups.com
It feels complicated with macros, but if it had syntactic support, say something like this:

@enum Country USA UK 

abstract Animal
type Cat <: Animal end
type Dog <: Animal end

extend Animal
    classification = "vertebrate"
end

Animal..classification       #vertebrate
Cat..classification          #vertebrate

extend a::Animal
    age::Int,
    weight::Float64,
    kilo = a.weight * 0.4535
end

c = Cat()
c..weight = 2.4
c..kilo

extend (Animal, c::Country)
    incharge::ASCIIString
end

(Animal, USA)..incharge = "Mike"

# 2 player game requiring any two animals.
extend (a::Animal, b::Animal)
    function play(toys::Int...)
    end
    
    function play(toys::ASCIIString...)
    end
end

d = Dog()
(c, d)..play(1, 2, 3)

#Traits
trait Movements
    pos::Point
    
    function run(speed::Float64, time::Int)
        #Change pos accordingly.
    end
    
    function walk(time::Int)
        run(1, time)
    end
end

extend a::Animal
    trait Movements
end

c..walk(10)
c..pos

------------------------

Would this feel natural?

All of this could be implemented using multiple dispatch. I'm using the .. operator because these extended properties are different from the properties defined originally on the type and are only loosely associated with the objects.

Or something like this. 


Tom Breloff

unread,
Jul 28, 2015, 2:24:58 PM7/28/15
to juli...@googlegroups.com
Here's one possible implementation of (some of) your example.  I created 2 macros which could simulate multiple inheritance... both are probably implemented in a nicer way already... but I just wanted to show something simple.  The parent macro converts a type expression into an assignment "name = fields_block".  The inherit macro injects the parent fields into the type expression.

#-----------------

isexpr(x, head) = isa(x, Expr) && x.head == head

macro parent(expr::Expr)
  @assert isexpr(expr, :type)
  tname = expr.args[2]
  tfields = filter(x->!isexpr(x,:line), expr.args[3].args)
  quote
    $(esc(tname)) = $tfields
  end
end


macro inherit(expr::Expr, parents::Symbol...)
  @assert isexpr(expr, :type)
  for parent in parents
    append!(expr.args[end].args, eval(parent))
  end
  quote
    $expr
  end
end

#-----------------

abstract Animal

@parent type CatOrDog
  age::Int
  weight::Float64
end

kilo(x) = x.weight * 0.4535

@inherit type Cat <: Animal end CatOrDog
@inherit type Dog <: Animal end CatOrDog

abstract Country
type USA <: Country end
type UK <: Country end

incharge(a::Animal, ::USA) = "Mike"
incharge(a::Animal, ::UK) = "The Queen?"

play{T<:Animal}(a1::T, a2::T) = "no toys? boring"
play{T<:Animal}(a1::T, a2::T, toys::Int...) = "playing with Ints: $toys"
play{T<:Animal}(a1::T, a2::T, toys::String...) = "playing with Strings: $toys"

play(a1::Cat, a2::Dog, args...) = play(a2, a1, args...)
play(a1::Dog, a2::Cat, args...) = "Grrrrrrrrrrrrr.... meow"

cat = Cat(10, 8.0)
dog = Dog(20, 15.0)

#-----------------

julia> kilo(cat)
3.628

julia> kilo(dog)
6.8025

julia> incharge(cat, USA())
"Mike"

julia> incharge(cat, UK())
"The Queen?"

julia> play(dog, dog)
"no toys? boring"

julia> play(dog, cat)
"Grrrrrrrrrrrrr.... meow"

julia> play(dog, dog, "bone", "ball")
"playing with Strings: (\"bone\",\"ball\")"

julia> play(dog, dog, 1, 2)
"playing with Ints: (1,2)"



Vinuth Madinur

unread,
Jul 28, 2015, 3:00:37 PM7/28/15
to juli...@googlegroups.com
:) That was a fun read. I see what you mean. Especially on play function calls, my approach feels cumbersome.

There is a difference in what I am referring to however. I'm talking about associating new fields with types that have already been defined elsewhere. And provide for associating data with even abstract types or functions and not just instances. 

For example, let's say  there is already a type defined for points.

type Point
     x::Float64
     y::Float64
end

But now in a program, let's say I also want to keep note of the coordinate system a point was created for, then I could do:

extend p::Point
      system::ASCIIString
end

a = Point(12.45, 167.32)
a..system = "polar"

----

Things I am not yet clear on in my approach are: where these extended fields are stored (currently they are not stored as part of the objects memory), how it impacts serialisation, constructors, etc., But essentially these can be loosely associated metadata / methods on an object or tuple of objects and not be part of the object memory at all. (Hence the .. operator and not the . operator)

Tom Breloff

unread,
Jul 28, 2015, 3:15:13 PM7/28/15
to juli...@googlegroups.com
For this Point example, it could be similar to the Dog/Cat definitions:

# ----------------------

abstract Point
@parent type PointParent; x::Float64; y::Float64; end
@inherit type Cart <: Point end PointParent
@inherit type Polar <: Point end PointParent

a = Polar(12.45, 167.32)
b = Cart(10., 20.)

# Now you can do whatever you want with them... generic methods for all points, or special methods for specific types:

typestr(p::Point) = string(typeof(p))
typestr(p::Polar) = "hello world"

# ----------------------

julia> typestr(a)
"hello world"

julia> typestr(b)
"Cart"

julia> a.x
12.45

julia> b.x
10.0


Tom Breloff

unread,
Jul 28, 2015, 3:29:16 PM7/28/15
to juli...@googlegroups.com
Also here's another method of encoding this info, assuming you already have a concrete type somewhere.  I create a parametric wrapper for a Point object, and put the meta information in as the parameter.  See the "getx" function... you can still use dispatch to make this seamless for the user.  (Also note that if you forced putting method definitions inside the type definition, then I couldn't add the getx method after the fact as I have here.)


julia> type Point; x::Float64; y::Float64; end

julia> p = Point(1., 5.)
Point(1.0,5.0)


julia> abstract PointType

julia> immutable Polar <: PointType end

julia> immutable Cart <: PointType end

julia> immutable PointWrapper{T<:PointType}
           p::Point
       end

julia> polarwrapper = PointWrapper{Polar}(p)
PointWrapper{Polar}(Point(1.0,5.0))

julia> cartwrapper = PointWrapper{Cart}(p)
PointWrapper{Cart}(Point(1.0,5.0))


julia> getx(pw::PointWrapper) = pw.p.x
getx (generic function with 1 method)

julia> getx(p::Point) = p.x
getx (generic function with 2 methods)

julia> getx(polarwrapper)
1.0

julia> getx(p)
1.0



Vinuth Madinur

unread,
Jul 28, 2015, 3:44:44 PM7/28/15
to juli...@googlegroups.com
Shouldn't there be an easier way to do the above? And it's not scalable to add wrappers every time some need for some meta property arises. 

Also, the getx kind of functions can still be extended like so:

extend p::Point
     function func1(arg1, arg2) ... end
end

Now to extend func1:

extend p::Point
     function func1(args...) ... end
end

Also, I think the extend ... end blocks can be entirely done away with:

p = Point()
p..system = "polar"            #. Automatically creates a dynamic property "system" if it doesn't exist.

Tom Breloff

unread,
Jul 28, 2015, 4:22:02 PM7/28/15
to juli...@googlegroups.com
You're essentially asking for every type to be able to dynamically support dictionary-style access for fields, which means that many (most?) potential compiler optimizations aren't possible anymore.  If you need meta flexibility in this way, I wonder if it's acceptable to put the system info in a const Dict, which is effectively what you're asking for:


julia> const system = Dict{Point, Symbol}()
Dict{Point,Symbol} with 0 entries

julia> p = Point(1.,2.)
Point(1.0,2.0)

julia> system[p] = :polar
:polar

julia> system[p]
:polar


Vinuth Madinur

unread,
Jul 28, 2015, 4:55:07 PM7/28/15
to juli...@googlegroups.com
Effectively yes. But the problem with using just the Dict is, it won't provide type inheritance, etc., which multiple dispatch provides with a lot of flexibility.

So,

@enum CoordinateSystems Cart Polar

extend a::AbstractPoint
      system::Int
end

p = Point(1.2, 6.8)
p..system = Polar

would expand to something like this:
if !method_exists(getproperty, Tuple{AbstractPoint, Type{Val{:system}}})
    let
        local system::Dict{AbstractPoint, Int} = Dict{AbstractPoint,Int}()
        
        function getproperty(a::AbstractPoint, ::Type{Val{:system}})
            system[a]
        end
        
        function setproperty!(a::AbstractPoint, ::Type{Val{:system}}, val::Int)
            system[a] = val
        end
    end
else
    #Warn
end

p = Point(1.2, 6.8)
setproperty!(p, Val{:system}, Polar)

This way, multiple dispatch will help in making "system" field accessible on all subtype instances of AbstractPoint. Due to multiple dispatch it'll be possible to support this on parametric types, a Tuple of objects / types, etc.,  Better than just using Dict and provide a truly type hierarchy honouring system. 

I'm not aware of the performance implications of this and from that point of view this could be a horrible system. I don't know. But a Dict will be required only for instance variables, not for class (type) variables.

Jameson Nash

unread,
Jul 31, 2015, 2:32:50 AM7/31/15
to juli...@googlegroups.com
That example seems trivially equivalent to the earlier demonstrations of inheritance, with all of the usual issues and questions that involves. The best jump off point for that might be https://github.com/JuliaLang/julia/issues/8974, since any change to types needs to be fully integrated with all of the system, although there are others such as https://github.com/JuliaLang/julia/issues/5 that are likely relevant.

The current system is not perfect (for example, see https://github.com/JuliaLang/julia/issues/11452#issuecomment-125843759 for some efforts to better unify the system), but it's highly unlikely Julia will ever adopt dot-oriented syntax. To make that change feasible, someone would need to show that there is some functionality that you can't express in the current model (for example, your earlier claim that it would make it easier to implement traits, as opposed to the THTT, for example: https://github.com/JuliaLang/julia/issues/2345#issuecomment-55838269).

Stefan observed previously in this thread that your system was effectively providing two levels of dispatch. I'll conclude by noting that the `Module` object currently provides exactly this separation (albeit in a limited form, since there is no inheritance hierarchy) between resolving the generic function and picking the dispatch method.
Reply all
Reply to author
Forward
0 new messages