How is my code selecting dispatch for constructor?

Hello, I need some help in understanding how Julia is selecting the constructor for my struct.

Here is a brief context: I’ve been trying to think of ways to reduce the number of methods I have to code for a somewhat complex struct. So I decided to try something like f(x...) where I would try to accommodate various different ways to construct f.

A minimal example building an object foo

struct foo
    val::Float64
end

function foo(x...)
    println("Computing...")
    foo(prod(x))
end

This works fine, although I am not sure if it’s the most efficient way to do it or if it is fragile:

julia> foo(1.0)
foo(1.0)

julia> foo(1.0, 2.0, 3.0)
Computing...
foo(6.0)

My real code, however, uses parametric constructors. When I combine those things the error shows up:

struct bar{T}
    val::T
end

function bar{T}(x...) where T <: AbstractFloat
    println("Computing...")
    bar(prod(x))
end

Trying

julia> bar(1.0)

The function calls itself every time resulting in stack overflow.

My understanding is that Julia tries to find the most specific dispatch, why does it work in the first case but not in the second one?

Is f(x...) a bad idea in general?

Thank you!
Gustavo.

The reason there is a difference is:

julia> methods(foo)
# 3 methods for generic function "(::Type)":
[1] foo(val::Float64) in Main at REPL[1]:2
[2] foo(val) in Main at REPL[1]:2
[3] foo(x...) in Main at REPL[2]:2

julia> methods(bar)
# 1 method for generic function "(::Type)":
[1] (::Type{bar})(val::T) where T in Main at REPL[3]:2

function bar{T}(x...) overwrote the constructor for bar. The following works just fine, because it leaves the bar{T} constructor alone.

function bar(x...)
    println("Computing...")
    bar(prod(x))
end

julia> bar(1.0)
bar{Float64}(1.0)

julia> bar(1.0, 2.0)
Computing...
bar{Float64}(2.0)

Not in general, but sometimes…

1 Like