Allowing the object.method(args...) syntax as an alias for method(object, args ...)

This can’t and won’t work without a breaking change to the parser, since .. already exists as a function, and binds less tightly than function calls:

julia> Meta.@dump my_object..mymethod()
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol ..
    2: Symbol my_object
    3: Expr
      head: Symbol call
      args: Array{Any}((1,))
        1: Symbol mymethod

that is, this get’s parsed as my_object..(my_method()) instead of (my_object..my_method)()


Indeed, naturally I’ve tried:

..(obj, meth) = (args...; kwargs...) -> meth(obj, args...; kwargs...)

And it works, sorta, except the expression ends up requiring extra parentheses:

(my_object..mymethod)(a, b, c)

which gets very awkward when chaining. Also broadcasting doesn’t work.

Anyway, is it worth defending .. from breaking changes? Is there a way to find out if anyone uses it?

Nobody is going to break compatability just to make people used to class based OO happy. It’s just not going to happen.


Intervals.jl is fairly popular.


Why on earth is this thread still going on about major version-breaking .. changes or piping that is restricted to single-argument functions when CBOOCall.jl was already suggested in that comment? I don’t like making function names properties of a type in a non-OOP language (which is also why I consider this worse than simply inputting the first argument unusually like do-blocks), but if you’re going to do it, that looks like the way to do it. Only thing I don’t like about that package is that I still can’t tell what CBOO stands for.

1 Like

Actually its IntervalSets.jl (I always get this mixed up too)

And also more popular:

@uniment that’s 5% of the ecosystem. Hopefully you can see the difference between pragmatic solutions that are not perfect but can be implemented and merged, and nice ideas that can’t happen in reality.

Also, mandatory:


What CBOO means.

CBOOCall.jl has particular characteristics. For example you have to sort of “register” any method you want to include via @cbooify. If you are the author of the type then this seems pretty clear; I mean it’s a good idea to make clear which are “instance methods” anyway. If you are not the author, you can still @cbooify methods. But this seems less robust, especially if CBOOCall were used widely (it’s not.)

An example of why this might be convenient:

circ = QuantumCircuit(3)
circ.x(0) # apply an X gate at position 0

Adding a gate this way, and the name, is very standard in quantum computing (mostly in Python). In Julia world, everyone says, it’s not a problem it’s just a preference, you’ll get used to it, etc… So you do

using QuantumCircuits: x
x(circ, 0)   # or circ(x, 0) or whatever

But, importing a lot of functions with identifiers like x could cause headaches. So you could name the function xgate or something instead. Or qualify it: QuantumCircuits.x. But, these are less convenient. And these gates are used a lot. Obviously, similar examples will arise in other domains. If you give up this method of calling, you are giving up something concrete; it’s not just a matter of taste; red or blue.

There could be other ways to solve this particular problem of wanting many functions with very short names. I don’t recall finding any other contenders, but I may have forgotten.



  • The intent is not to treat functions as properties of types. This isn’t an OO language!
  • What is desired is not piping restricted to single-argument functions. We already have that and it sucks.
  • What is being discussed is a language feature to support common thought patterns, not a dozen different libraries and macros that solve the problem in different ways and that will never become a standard.

CBOOCall is hoping to solve a very specific problem: allowing functions or objects with very short names (e.g. x, y, and z) to be conveniently accessed from a module without polluting the global namespace. It’s spiffy, but not what we’re discussing. At least not what I’ve been.

But if we were trying to address that, @jlapeyre could’ve just used Julia’s let statement. Using CBOOCall’s example:

using QCircuit # don't export x, y, and z
let x=QCircuit.x, y=QCircuit.y, z=QCircuit.z
    add!(circ, x, 1)

This could be more convenient if Julia had some methods for object destructuring… or maybe for broadcasting access of properties, for example

..(obj, props::Tuple{Vararg{Symbol}}) = getproperty.(Ref(obj), props)
using QCircuit
let (x, y, z) = QCircuit..(:x, :y, :z)
    add!(circ, x, 1)

Or even, maybe Julia could do something in the same spirit as JavaScript’s with statement, to create a block where that namespace applies and outside which it doesn’t… perhaps

with QCircuit
    add!(circ, x, 1)

Then any objects and methods of the QCircuit module would be available, but only within that block. I’ve seen some gnarly Julia code where this feature would substantially reduce code complexity and interdependence.

… Anyway, I digress.

Ah. Unfortunate.

Very well.

I guess -- isn’t being used for anything. Maybe?


has a ring to it :thinking:

Some other possibilities: **, ||,

(Although I’d prefer || to be reserved for an operator with same precedence as addition, but sacrifices must be made)

We’re already on 1.8… queue meme of scheming raccoon

1)Conventions set by OOP languages and 2) easily colliding names that shouldn’t be encapsulated and exported by the module seem like reasonable justifications. For completion’s sake, I would only ask why circ(:x, 0) doing getproperty(QuantumCircuits, :x)(circ, 0) isn’t enough. My guess is that it’s type-unstable, but I think the compiler does extra tricks with getproperty so I’m not sure.

Then you already lost me at using UFCS to mimic OOP syntax.

  1. In some OOP languages, you can access encapsulated functions a = x.f and call it later a(y). Without the properties approach of CBOOCall.jl, x.f wouldn’t even have a valid meaning, and the parser would probably lower that to a getproperty call doomed to fail.

  2. Concerning autocompletion based on the first type alone, it would only be performant with type properties/class encapsulation. Searching every method ever to find the ones with the right first argument, especially when there are supertypes to check, is excessive work. CBOOCall.jl’s use of propertynames would be autocomplete’s best friend.

  3. OOP programmers won’t like using anything but . for this; .. is already pushing it and -- will be a non-starter. R is still getting complaints for using <- and -> as assignment.

  4. And the best for last, how would the parser tell if x.f(y) should be lowered to f(x, y) or if x really does have a callable field f accepting 1 argument? In fact, those can exist simultaneously:

struct X  f::Function  end
x = X(z -> 1)
f(x::X, y) = 2
println(x.f(0), ", ", f(x, 0))

There is Chain.jl and Pipe.jl, they handle piping to any functions iirc.

I wouldn’t write all that, I’d sooner import QCircuit as QC and write QC.x everywhere.


The main benefits to UFCS, IMO, have nothing to do with the object oriented paradigm. It’s just nice syntax that allows for noun→verb ordering, without being as limited as the pipe |>. Equating UFCS with OOP is a mistake imo. See this post:

I’m not convinced that’s necessary. Encapsulation isn’t needed frequently, and an anonymous function can be explicitly defined when it is.

Use of . should be limited to property getting. Keep it simple.

Macbooks are pretty powerful these days… I guess we’ll see. Likely more performant than my brain anyway.

Not convinced. I prefer + for string concatenation and * for string replication, but Julia uses * and ^ respectively. It’s not the end of the world.

But also, it’s a beneficial reminder to OOP programmers that this isn’t an OOP concept; we are calling a globally accessible method with the object as the first argument, *not* calling a method that “belongs” to the object. Hence why syntax other than . is good, to disambiguate for the compiler and also for the person between property getting and method calling.

But please, characters that are easy to type without errors. |> is such an error-prone sequence of characters.

That’s why I was proposing .. to disambiguate from property getting. Or perhaps --.

Yes, there are about half a dozen packages that solve the same problem in slightly different ways, so we’re unlikely to have a standard anytime soon unless it’s incorporated into the language.

Oh. Oh my. Oh my oh my. I just learned about Julia’s new destructuring syntax. :clap: :clap: :clap:

(; prop1, prop2, prop3) = my_object # prop1 == my_object.prop1, etc.

Game. Changer. Sí se puede.

Strangely it doesn’t seem to work for destructuring modules… that seems like a bug (considering that my destructuring code above does work on modules, and supposedly they work on the same methods).

If it worked, @jlapeyre could write:

let (; x, y, z) = QCircuit
    add!(circ, x, 1)

Wonderful syntax :pinched_fingers:

1 Like

Your proposal is logically consistent as it knowingly eschews OOP, so pardon my misunderstanding because this thread was initially about implementing such syntax for OOP users :melting_face:, who seem to be the vast majority of people requesting this syntax.

At least do-blocks let you input an anonymous function into the conventionally first argument of higher order functions without needing to assign the anonymous function to a variable or the anonymous function splitting the higher order function call in two. By comparison, there is no benefit to having a special syntax for x--f(y) but not y--f(x) to mean f(x,y); in fact we have Chain.jl and Pipe.jl that can differentiate and do both.

What better reminder of f being a global function that does not belong to x than writing f(x, y)?

1 Like

Actually, x--f(y) meaning f(x, y) brings the benefit of encouraging what is frequently the most reasonable argument ordering, just as it’s reasonable for the subject of a sentence to precede the verb and predicate. At least that’s the case in the spoken languages I know.

Chain.jl, Pipe.jl, etc. offer flexibility over which argument the object gets placed into, rather than forcing it into the first argument. So you get the flexibility to put the predicate before the verb and subject; you can also put an adjective first, or an adverb first. But in exchange for this extra flexibility comes extra awkwardness from having to type more characters and having to think about which argument to place the object in every time. It’s a trade-off which frankly isn’t worth it most of the time, especially if argument orderings are defined well.

Well I would hope the purpose of the language isn’t just to remind me that functions don’t belong to objects! :sweat_smile:

Consider the following instructions describing a sequence of transformations to a fruit:

Take an orange. Peel it with your fingernails. Split it. Put it in a bowl. Eat it slice by slice.

Imagine if the English language insisted I present this sequence as:

Eat the (put the (split the (peel an orange with your fingernails)), in a bowl), slice by slice.

It’s in an ordering that hardly makes sense, all because we wanted a reminder that verbs don’t belong to nouns. A bit masochistic if you ask me!

And that’s where noun→verb chaining with UFCS shines. It’s piping, but with predicates, adjectives and adverbs.

I take this back. It’d be easy enough for my_object--mymethod to capture my_object in an anonymous function:

my_object--mymethod # yields an anonymous function,
(args...; kwargs...) -> mymethod(my_object, args...; kwargs...)

Okay so I tried it again, and destructuring actually does work on modules. Not sure what went wrong the first try.

So with that said, @jlapeyre give this approach a shot and see how you like it:

using QCircuits
let (; x, y, z) = QCircuit
    add!(circ, x, 1)

What about the following translation for your orange example?

let it = orange
    it = peel(it, fingertips)
    it = split(it)
    it = put(it, bowl)
    eat(it, slice_by_slice)

Given that I actually like the doto macro from Clojure – making Java interop quite nice despite Clojure strongly favouring functional programming:

(doto orange
   (peel :fingernails)
   (put :bowl)
   (eat :slice-by-slice))  ;; only works by side-effect - doto returns final obj itself

Should be rather easy to replicate with a macro in Julia…

That looks like Chain.jl’s @chain.

@chain orange begin
1 Like

Except that doto works purely by side-effecting the object and does not pass the results along, i.e.,

    it = orange
    peel(it, :fingernails)
    put(it, :bowl)
    it  # returns the updated object at the end

Clojure also has chaining macros -> and ->> which pass the results of each function as the first and last argument respectively. doto is mostly useful for Java objects with side-effecting methods predating fluent interfaces, e.g., not returning the object itself.


I’d much prefer


# note that by convention, Julia methods which 
# modify their arguments have names ending in "!"

Code continually reassigning it to the newest orange transformation isn’t particularly insightful. Waste of space and time.

I didn’t know that about Clojure. Excellent example of a functional language implementing UFCS for the betterment of mankind.

As an aside, it’s an interesting idea to have syntax ->> for inserting the object as the last argument. How frequently does this end up being used?

Hah, the -- operator is growing on me.

Dot . to get properties of this object; dash -- to invoke methods on this object.

Not to be confused with Morse code.

1 Like

That’s not really UFCS: afaict, Clojure’s -> is Chain.jl’s @chain, and ->> is requested in Macro to pass last argument · Issue #48 · jkrumbiegel/Chain.jl · GitHub

Fair. It’s as close to UFCS as you’ll get for a LISP.

Point is that they felt it wise to insert the object as the first argument in a similar fashion to UFCS.