Trouble defining a function working on both a type and its Arrays

question

#1

Dear all,
I took about a year of a break on Julia. In my old code I used a lot of functions that worked both on a Type and an Array of the type (you may discuss whether that’s good, I’d like to have something that “automatically vectorizes” in a certain sense). So a lot of my functions look something like this (tried to narrow it down to an MWE)

import Base.show

abstract type A end
struct B <: A
    n::Float64
    B(v::Float64) = new(v)
end
function show(io::IO,b::B)
    print(io,"~ $(b.n) ~")
end

function f(b::B)
    return B(sqrt(b.n))
end

function g{T <: A,N}(bpA::Union{T,Array{T,N}})
    return f.(bpA)
end

p1 = B(0.1)
p2 = [B(0.2),B(0.3)]

c = g(p2)
print(c)
g(p1)

In my old code The Union even had round braces – okay, that was easy to fix, but while the last command g(p2) does work (print(c) beautifully prints an array of Bs) the last one does not. As far as I understood and wanted to use, the Union in g lets it accept both B variables (like p1) and B arrays (like p2) – but I am sitting here for two hours now and I can’t get the first one to work, i.e. letting it accept also just a T <: A, though as far as I understand, the Union should do exactly that. The error message I get is “ MethodError: no method matching g(::B)” and stating g as a candidate, though never indicated, where it does not match.

Long story short: Where Am I wrong here? In my old code, back in January 2017, this worked (with rounded braces) like a charm.

PS: I know, for the MWE a solution is to define g only to T<: A and use g. – but I am more interested in the general solution.


#2

Try something like

function g(bpA::Union{T, Array{T,N}}) where N where T<:A
    return f.(bpA)
end

or

function g(bpA::Union{<:A, Array{<:A,N}}) where N
    return f.(bpA)
end

and also take a look at
https://docs.julialang.org/en/stable/manual/methods/#Parametric-Methods-1


#3

The error you’re getting is due to the parameter N not being defined when you call the function with a variable of type T and not Array{T, N}.

This is fixed on julia 0.7 if you don’t actually use N when it’s not defined.

Meanwhile you could either define two methods g(bpA::A) and g(bpA::Array{A,N}) where N , or simply define g(bpA::Union{A, AbstractArray{A}}) since you’re not actually using N in the function


#4

The common style in Julia these days is to write scalar-oriented functions for scalars, and then to explicitly vectorize it on the caller end using . broadcasting.

What you are doing is to vectorize implicitly. There’s not necessarily anything wrong with that, but since it goes a bit against the common idioms, it might surprise users, and could introduce uncertainties or inconsistencies, such as do you always vectorize implicitly, what if you forget it for some methods? In compound expressions, you might miss out on loops fusing, because if you forget a dot, it will still work, just slower.

I would recommend that you try to write scalar-oriented code to work just for scalars, and then explicitly use the dots, and see if you end up liking that style.


#5

Ah, I thought, the N in front did define the N. Interesting to learn, that it does not.


#6

Thanks for the recommendation; I actually did that because I am coming from a Matlab code where nearly everything is implicitly vectorize. You are right, maybe that would be a nice and goot idea in order to keep the (then scalar) code cleaner.


#7

The answers of @gsoleilhac and @pfitzseb both answer my question. Since @pfitzseb was first, I’ll accept his answer as the solving one.

However, I also took the advice of @DNF carefully into account and redesigned my functions to work on data and not vectors of data (implicitly) but introduce and explicit vectorisation for that.

Thanks to all of you three for the help.