# How to distinguish an array from a tuple when doing a copy / assignment

I have a function for which one of the arguments may be either an array or a tuple, though its usage internally is tuple-like. If x is an array that then I will have the line
`y = copy(x)`
but if x is a tuple, copy will not work so I need to simply say:
`y = x`
The problem is: how can my code distinguish the two? I tried using `typeof()`, but that does not return a string, so I do not know how to interrogate the result of `typeof()`. (I looked at https://docs.julialang.org/en/v1/manual/types/ but I cannot find anything there on how to process the output of `typeof()`).

EDIT: never mind, a âsplatâ seems to work on either:
`y = tuple(x...)`
EDIT 2: So I will delete this in a while unless someone asks to keep it.

You can check if itâs a subtype of a `Tuple` or equal to a specific type, like this:

``````julia> a = (1, 2, 3)
(1, 2, 3)

julia> typeof(a) <: Tuple
true

julia> typeof(a) == Tuple
false

julia> typeof(a)
Tuple{Int64,Int64,Int64}

julia> typeof(a) == Tuple{Int64,Int64,Int64}
true

julia> typeof(a) === Tuple{Int64,Int64,Int64}
true

julia> typeof(a) == Tuple{T, T, T} where T<:Int64
true
``````

Why do you need to copy the array? Canât you just write `y = x` in either case? I mean, if you plan to mutate `y` thatâs maybe not a good idea, but you cannot mutate tuples, so I presume that youâre not going to do that anyway.

3 Likes

You could define a method of `copy` for tuples:

``````julia> Base.copy(t::Tuple) = t

julia> copy((1,2))
(1, 2)
``````

I canât think of a reason why this would be a bad idea, but that doesnât mean there isnât one!

Good point, I had a blind spot. I knew the following code gives nasty side effects in the calling function:

``````function f(x, y, some_more_params)
if some_condition
x[1] = y[1]
end
do_something
end
``````

But this (which is what I wanted to do) is actually safe. My âblind spotâ is that I forgot it was safe, as I was telling myself ânever modify an array argument inside a functionâ:

``````function f(x, y, some_more_params)
if some_condition
x = y
end
do_something
end
``````

Still, now that I have forced x to be a tuple, I feel I might as well leave it in my code, which now looks like this:

``````function f(x_initial, y, some_more_params)
x = tuple(x_initial...) # a "splat" to force a tuple
do_something
if some_condition
x = y
end
do_something
end
``````

(EDIT: And in terms of readability, I think it is helpful to distinguish between the default/initial value of `x`, and the âcurrentâ value of `x`).

Thereâs nothing inherently wrong with modifying an array inside a function (almost everything should be happening inside functions anyway), as long as you are deliberate about it. Itâs one of the most common performance techniques. Just remember to put a `!` at the end of any function that modifies inputs, to make that clear to the caller.

1 Like

Type piracy?

1 Like

Yes Iâm aware that itâs type piracy.
But why, for example, is `copy(t::Tuple)` not defined in Base?

1 Like

I am not a fan of turning arrays into tuples.
At least add a function barrier to mitigate the inevitable type instabilities.

It was defined in the past, but got removed in https://github.com/JuliaLang/julia/pull/15675 by Jeff:

I think itâs better not to define `copy` on objects where it doesnât make sense and doesnât do anything

4 Likes

There is actually quite a number of issues/PR related to copying immutables:

• implement `copy(::Void) = nothing` (closed PR, contains an interesting discussion related to âalias-stabilityâ)

If you have copy(f(x)), and f returning nothing is a problem, then failing in copy catches the problem sooner than having copy propagate the value. I think if some code is bothering to copy something, itâs expecting a mutable collection. (Jeff)

• Define `copy` for `Char` (closed PR)
• `copy(x::Symbol)` isnât defined (closed issue)

Generally a copy method for a (mutable) container should only copy the container structure, and not values inside. (Jeff)

• no method `copy(::Missing)` (open issue)
• `copy` not implemented for `String` (open issue)

Iâve tried to avoid defining copy for immutable objects. How did this arise? My view is that code calling copy without knowing whether the argument is mutable canât really be generic. (Jeff)

If youâre copying something, then it must be because youâre going to mutate itâotherwise why copy it? But if some of the values are immutable, then thatâs going to fail anyway, so what kind of code needs this? (Stefan)

2 Likes

Well you said you couldnât think of any reason why it would be a bad idea, so I thought maybe you had a glitch and forgot it was type piracy.

1 Like

Yes fair enough. The question is whether type piracy is always bad. I thought I remembered Jeff recently saying that it wasnât.

1 Like

Yeah, I guess itâs not automatic, but itâs a red flag, I think.

I guess it gets problematic if you e.g. put this into a library which is used by someone who finds and uses the type pirated method and doesnât know itâs actually not in `Base`. That might cause some headaches when in another ensemble of packages this particular method is magically missing although itâs a `Base`-method dispatching on `Base`-types.

Not sure how likely this is

I have to say that I disagree with the founders (i.e., Jeff and Stefan) on this one (what means I am probably wrong, XD). My reasoning is the following:

1. `deepcopy` is defined by default for both mutable and immutable types. What is the reasoning for having a distinct policy for `copy` and `deepcopy`? Considering that `deepcopy` can be as misleading, my first PR to a Julia library was exactly making `deepcopy` raise an exception on `JuMP.Model` because it does not copied a C pointer resource so it was not a true `deepcopy`.
2. It is just a more elegant way of doing `isimmutable(x) || (x = copy(x))` or `y = isimmutable(x) ? x : copy(x)`.
3. As pointed by (2) it is just a matter of convenience, not defining `copy` will do little but make programmer scratch their head and sometimes they will be happy that some bug did not propagate, others they will just lose some time thinking if it is better to put an `if`, or define `copy` for some immutable type, or just change statement order, and if they did assume wrong that copy would not raise exception over immutable types and now they need to check their code for it.
4. As `copy` is not defined for default for immutable types but also is not guaranteed to not be defined for immutable types, programmers cannot assume that arbitrary immutable types in the wild will or not provide them.
5. Programmer making use of type piracy to âinoffensivelyâ create `copy` methods for convenience can change the error that is raised in some situation, making harder to identify that is the same problem as others had.
6. The current state is not completely coherent either, `Int`, `Float64`, and others have `copy` defined even being immutable types (and will raise different errors when passed to a method that often throws on `copy` for arbitrary immutable types).

Again, not the strongest case, but I feel like it was just a bit more convenient if `copy` and `deepcopy` had the same policy. Not the sufficient, probably, for meriting the change now they are already this way.

2 Likes

I wouldnât recommend this for code where you care about performance, as it will be slow both for arrays and for longer tuples.

This code will perform badly for arrays because the output type depends on the number of elements of the array, so the compiler wonât generally be able to figure out the type of `x`. And it will perform badly for long tuples because splatting large numbers of arguments (more than I think 16) will generate less efficient code. Thatâs due to an intentional trade off in the compiler to avoid spending a long time generating code for functions with huge numbers of arguments.

As youâve already discovered, doing `x = y` seems to be totally fine for your use case, and it will perform better too. So Iâd recommend just doing that

1 Like