Tutorial: Efficient and safe approaches to mutation in data parallelism

That’s a good point that touches the corner of Julia I wish I can ignore :slight_smile: Basically, the code without tuple-wrapping is working “by accident” (in some sense). It’s actually nothing to do with parallelism. Consider the implementations of collect with and without the tuple-wrapping;

julia> function collect1(xs)
           ys = Any[]
           for x in xs
               append!(ys, x)  # no tuple
           end
           return ys
       end;

julia> function collect2(xs)
           ys = Any[]
           for x in xs
               append!(ys, (x,))  # tuple
           end
           return ys
       end;

As you’ve observed, there is no difference if the input is a collection of numbers (which is a singleton collection of itself):

julia> collect1(1:3)
3-element Vector{Any}:
 1
 2
 3

julia> collect2(1:3)
3-element Vector{Any}:
 1
 2
 3

However, your version won’t work when each element is also a collection (note: Base.collect(1 => 2) == [1, 2]):

julia> collect1(x => x^2 for x in 1:3)
6-element Vector{Any}:
 1
 1
 2
 4
 3
 9

julia> collect2(x => x^2 for x in 1:3)
3-element Vector{Any}:
 1 => 1
 2 => 4
 3 => 9

In other words, you are observing that

for e in x
    ...
end

and

for e in (x,)
    ...
end

are equivalent if x isa Number.

So, in the examples using Int, you are correct that there is no need to use a tuple. However, since this is a tutorial, it is important to demonstrate the fundamental pattern in parallel computing:

  1. Create a singleton solution (e.g., the singleton tuple)
  2. Combine the solutions (e.g., append!)

I was hoping that explicitly constructing the singleton solution like (x,) helps the readers to familiarize themselves with this pattern.

5 Likes