It’s particularly important to emphasize how Julia is not a single dispatch language, and the first argument to a function is not special.
The do
statement would say otherwise, but apparently we limit first-argument “specialness” to anonymous functions only.
I think you might be misreading Bjarne Stroustrup comment. Yes I agree that is a bad idea to always assume that there is one “most-important” argument, but here we are talking about having more options, not less. Nobody wants to remove the standard notation, just adding a new equivalent one with some known advantages, in some of the cases. It’s up to people then to use the best one in each case.
I sympathize with this.
There’s a fair bit of anti-OOP pearl clutching in this thread, seemingly fearing that any privileged treatment of data will unleash the OO demons. I’m not suggesting to make methods “belong” to objects or to adopt JavaScript’s prototype inheritance model! But providing syntax sugar to allow UFCS does not an OO language make. The fear that angry hoards of OOP brutes will descend from the hills to defile Julia’s functional innocence is overblown imo.
Fun note. While the first argument is not *always* the most “important” (whatever that means), it frequently is. That’s why we choose to put it first in the argument list!
@jzr,
In the SO post, the person object has a fixed set of methods on it that work in the postfix position. In UFCS, all
methodswith(Person)
can go in the postfix position, so it’s compatible with Julian dispatch, not less powerful.x.add(y)
is justadd(x,y)
.
I love it. Notably, this is still multiple dispatch, just with syntax sugar. Methods do not need to “belong” to objects as in OO languages.
I don’t think there’s much danger of users thinking that a function has an owner since that concept doesn’t exist in Julia.
Bingo. Spot on.
To disambiguate from property getting, something like ..
could be used to denote invoking a method. For example, my_object.property
versus my_object..mymethod()
.
Why have syntax sugar for UFCS? Because sometimes thoughts flow as verb→noun (e.g. read(book)
), but sometimes they flow as noun→verb (e.g. book..read()
), depending on the context. The purpose of a language of course is to allow thoughts to flow as naturally as possible out of the meatbrain and into the computer, from left to right, so whichever order makes most sense in the context the language should try to support.
It’s frequently the case that we will transform an object several times before continuing on; UFCS simply acknowledges this. Example was given, dish = egg..crack()..scramble(bowl)..cook(:high)
, rather than dish = cook(scramble(crack(egg), bowl), :high)
. In the case of chaining, the noun that is being worked on as it undergoes transformations is front of mind; being forced instead to think first about the verbs (in reverse order of course) is frequently unnatural.
Julia already provides the pipe operator |>
for exactly this reason—although unfortunately it’s very awkward to type and it’s underpowered, being unable to pass more than one argument. In other words, the authors of Julia already acknowledge the benefits of noun→verb ordering and operation chaining, but nobody likes the implementation and people keep complaining because UFCS already provides a better solution in other languages.
Benefits to UFCS:
- the ability to think of the noun first and the verb next,
- the ability to chain operations on successive transformations of an object (similar to function composition or piping but with more arguments), and
- autocomplete on what methods are available on that object while typing the method name, ideally showing the most-type-specialized functions at the top of the list. This seems significantly easier and more convenient than the ?(x, …) notation being worked on.
Julia’s beloved features of composability, multiple dispatch, functional style, etc. are left unchanged. UFCS might encourage people to place the most-important argument (if one exists) first, but that doesn’t seem like a bad practice anyway .
Examples:
my_object.mymethod # ERROR: type MyStruct has no field mymethod
my_object..mymethod(1, 2, 3) == mymethod(my_object, 1, 2, 3) # true
[1, 2, 3]..push!(4).^2 == [1, 4, 9, 16] # true
"Hello, world!"..split(",")..replace.("o"=>"e")..join(":") # "Helle: werld!" (chaining & broadcasting)
document = parsexml(xml_string) # using EzXML
document..root()..firstelement()..setnodecontent!("Hello, world!") # Navigating tree
Thoughts welcome