Argument destructuring and anonymous functions

Dear all,

It seems that argument destructuring (https://docs.julialang.org/en/v1/manual/functions/index.html#Argument-destructuring-1) works for named but not anonymous functions (see examples below). Is there a reason for that, or am I just missing something as a newbie?

Thanks a lot!

# Works
f1((x1, x2)) = x1 + x2
f1([1, 2])

# Works
function f2((x1, x2))
    x1 + x2
end
f2([1, 2])

# Does not work
(((x1, x2)) -> x1 + x2)([1, 2])

# Does not work
function f4()
    function ((x1, x2))
        x1 + x2
    end
end
f4()([1, 2])
1 Like

It is working, but syntax is a bit different:

(((x1,x2),)->x1+x2)([1,2])

Otherwise the tuple will not be recognized.

4 Likes

Another way of thinking about this is that () is not the tuple operator, , is. Adding more parentheses to (x, y) -> x + y to get ((x, y)) -> x + y doesn’t change anything, because in general adding more parentheses to an expression in Julia doesn’t change what that expression means. If you want to express that the first argument is itself a tuple, you need that extra comma: ((x, y),) -> x + y.

4 Likes

It is an unfortunate ugly case when there is just one parameter, and it is a tuple. If there are two parameters that are both tuples, the notation is the one I would expect:

julia> a = repeat([(1, 2)], 4);
julia> b = repeat([(3, 4)], 4);
map((((x, y), (i, j)) -> (println(x, y, i, j))), a, b);

I have experience in Haskell, so I get confused all the time I am mapping over a single tuple and things do not work as I expected (i.e., without needing the external parenthesis with trailing comma). However, Haskell does not have this problem because:

  1. The language itself does not provide a single element tuple.
  2. The language provides an extension where the trailing comma have a complete different meaning (it creates a function which takes parameters to fill the empty spaces between commas and commas, or commas and parenthesis).
  3. In Julia, the Tuple type is tied to the concept of the arguments of a method/function/“clojure body”, so the one-element tuple is necessary because there are functions that take a single argument. This connection of concepts (parameters list and Tuple type) does not exist in Haskell (AFAIK).

Thanks to you all for your kind and helpful feedback!

It is unfortunate, since one of the main usecases for tuple destructuring is crafting pipelines, exactly a case where having multiple arguments unpack from a single value can be very useful!

On the other hand, at least it’s not too ugly, and, in my opinion, it’s better than special-casing the parentheses syntax for one-line anonymous functions. I can’t think of a preferable solution, and at least it’s only one comma’s worth of ugliness.

1 Like

One thing that helped me a little was to notice that map can take a variable number of streams to operate on (again differently from Haskell, if I remember right). Many times I was zipping many streams into a stream of tuples to then passing the stream of tuples to a map with an anonymous function that deconstructed each tuple to operate over its fields, when instead I could just pass the multiple streams as arguments to map (and deconstruct nothing in the anonymous function passed to map, but just take multiple parameters).

1 Like

Thanks for the reminder. I remember having seen that before, but I’d forgotten!

And now, fortunately, that functionality is also coming to mapreduce in v1.2.