Confusion with ... operator and dispatch in Julia

question

#1

Here is example Julia code that reverses element of touple that I’m trying to understand. It must be my lack of experience but it’s extremely confusing to me why this is working at all.

revargs() = ()
revargs(x, r...) = (revargs(r...)...,x)

reverse(t::Tuple) = revargs(t...)
julia> data = (1,2,3,4)
(1,2,3,4)

julia> reverse(data)
(4,3,2,1)

I’m trying to understand two things:

  • how … operator works here to produce reversed result.
  • why it it necessary to define revargs() = () even though it does not look like it is being called to produce the result of reverse(data)

Can you please explain to me what happens step by step after I call reverse(data)?

I’m sorry if my question is to broad or unclear but i’m really stuck and don’t know how to find the answer.


#2

I suggest you ask Julia what is happening by putting in some debugging code; for example,

reverse(t::Tuple) = (println("Entering reverse(t::Tuple) with t = ", t); revargs(t...) )

#3

Thank you for trying to help me but I have tried your suggestion and it did not produce any explanation.
Am i doing something wrong?

revargs() = ()
revargs(x, r...) = (revargs(r...)...,x)

reverse(t::Tuple) = (println("Entering reverse(t::Tuple) with t = ", t); revargs(t...) )

data = (1,2,3,4)

Here is the output of reverse(data)

julia>  reverse(data)
Entering reverse(t::Tuple) with t = (1,2,3,4)
(4,3,2,1)

#4

Put the printing in revargs. Print at least x and r. You could also capture the result in a variable and print it, too, before returning it.


#5

This is essentially reversing a tuple by recursively moving the first element of a sub-tuple to the last element of the returned tuple. Note that when you call r... in a function argument, this means that r is an iterable object within the context of that function. When you do r... from within the function in the call of another function, this converts the single iterable argument r into multiple arguments.

revargs() = () gets called when the last element x gets placed at the end. When this happens, there are no more arguments r to get placed into revargs(r...)... so one must define revargs for zero arguments.

I think this sort of code looks rather strange to those of us coming from object-oriented languages such as C++ but it is commonplace in functional languages such as Haskell. Admittedly, recursive code sometimes confuses the hell out of me.


#6

Exactly: I said “for example”; the idea was that you would put similar output in all of the functions, so that you could see the whole process going on.