Argument ordering and interpretation

Hi!

I just saw a tweet by @StefanKarpinski with the very well-written and informative summary of new features offered in the upcoming 1.7 release. As always, one of the reasons why one may want to switch to Julia is these types of well documented changes and its welcoming community.
As a newcomer to Julia, I would like to point out a couple of things related to argument ordering of specific functions that have similar functionality and procedural meaning that other mewcomers may have noticed too. Am not sure whether this is the right place to share these thoughts with the community, but I guess being in Julia “discourse” means exactly that.

So, while learning Julia, one of the first things a newcomer comes across is push!(B,A) and that it takes two arguments A and B and that in order to memorize and use it later on, one can “interpret” it as “push A into B” (the documentation says “Insert one or more items in collection.” where “collection” is B and A are the “items”). All good and well and, being a newcomer, I started to think that when there is a transitive meaning of the activity that the function expresses, then the accepting side is the first argument; I created a concept along the lines of an abstract interpretation scheme such as: <activity> <acceptor_B> <object_A>.

Then, being a computational linguist and trying to learn how to handle strings, I learned occursin(A, B), where the opposite holds:
“check whether A is in B” and started to revise my initial strategy above: <activity> <object_A> <acceptor_B>.

Then, replace(A,B) has the acceptor on A and the pairs of replacements are in the B position, so <activity> <acceptor_B> <object_A>.

In other words, it may be frustrating at times to try to remember what the acceptor and the object that the function applies on relatively to their argument position. I feel there is some inconsistency, but am sure that there is a good reason that this happens. However, I feel the need to share it with you, since after almost 1 year of using Julia on a 2 times-a-week-basis (much more frequently than I used to before last year) I still try to remember the argument ordering in such cases.
As a newcomer, I suspect that people have been facing the situation I describe unless it is my bad English that makes it harder.

Alex

3 Likes

Good news and bad news: you remember push! wrong. It’s push!(collection, elements). Luckily, that means there’s a pretty consistent pattern: if the function can be pronounced x function y in English, then function(x, y) is the order of the arguments (x in y). Otherwise, functions on collections generally have the collection first. Especially mutating functions almost always list the mutated argument first.

6 Likes

The convention for mutating functions (those with an exclamation mark, like push!) is that the argument that’s being modified is placed first, or at the beginning if multiple are modified (which is rare).

See also this section about argument ordering of the style guide:

https://docs.julialang.org/en/v1/manual/style-guide/#Write-functions-with-argument-ordering-similar-to-Julia-Base

6 Likes

Thanks about the correction related to push!(), @gustaphe ! I edited it above. However, the collection B is in the first position and that is why the interpretation scheme I described had been correct; namely, push A into B when push!(B,A). I cannot pronounce the function in a way that is consistent with the argument ordering. So, the push!(x,y) cannot be translated in that case as x push y (or x is pushed to y in the passive voice to be closer to actual language usage), unless there is some way of translating it that I am missing.
However, the rule of thumb that you suggest for when one deals with collections, namely that collections always come as the first argument of functions, makes sense. Thanks! I will keep that in mind.
Thanks for the link @Sukera ! It looks promising!

Exactly because “element push collection” is not an English sentence, there is no reason to expect push!(element, collection). One could imagine a function pushed_into, which at least to me looks like it would have the syntax pushed_into(element, collection) because “element pushed_into collection” sounds like English.

Note that if pushed_into mutates collection, it breaks two conventions:

  • Mutating functions have a !
  • Mutating functions have their mutated argument first

To fix this I would suggest renaming the function and changing the order of the elements. So essentially push!.

2 Likes

Interesting discussion…Many years ago, I started with Prolog and due to my background in logic during the first time of learning more procedural languages I mistakenly interpreted a predicate such as push!(x,y) the way you describe it; namely x pushes y (by the way, writing push(x,y) in the so-called polish notation in logic allows this type of interpretation) and not as a directive of the type go and push x to y. So, assigning procedural meaning to the function predicates that is usually the case in other languages, such as python and R, seems to collide with the more logical approach to function predicate interpretation that you describe (or probably that is also common among the Julia practitioners) and that I also used to feel comfortable with.

PS: I guess the keyword to remember here is the difference between mutating and non-mutating functions.

Personally I parse a mutating call

push!(x, y)

as a mutating assignment

x push= y

which makes the argument order consistent with += etc.

3 Likes

except when there is a function involved, like map!, then the convention is that the function is the first argument, output the second. This allows the use of the do syntax:

julia> map!(output,[1,2,3]) do x
           x^2
       end
3-element Vector{Float64}:
 1.0
 4.0
 9.0

where the do block replaces the first argument.

1 Like

Thanks!

This one has annoyed many people, to the point that contains, which just flips the order of the arguments, was added in Julia 1.5. Cf Bring back contains(haystack, needle) · Issue #35031 · JuliaLang/julia · GitHub.

1 Like

My mnemonic:
since there is also pushfirst!, I translate push! in the append sense.

push!(collection, items...) -> collection
append!(collection, collections...) -> collection

and yes, the first argument gets modified.

1 Like

After I finally got used to occursin :sob:. But contains makes much more sense, I’m slowly changing my code over.