A function in a separate module that annotates a parameter with an abstract type will throw an error when I pass a subtype.
Code below is summarized. Complete code is in following sections
# Module A
abstract type State end
# Module B
using .A
struct transiting <: State
from::Vector{<:Real}
to::Vector{<:Real}
end
# Module C
using .A
function foo(op::State)::State
return op
end
# Module Main
using .A, .B, .C
t = transiting([0.0, 0.0, 0.0], [1.0, 0.0, 0.0])
foo(t) # error
The error received is MethodError: no method matching foo(::Main.Example.Subtype.transiting)
I was expecting to be able to pass any subtype through, as long as the struct declared what its super type was. This works as expected as long as the subtype and function that annotates the supertype (modules B and C) is placed in the same module, which doesn't make sense to me.
I have tried including all modules for both function declaration and subtype declaration and that doesn't work. I may just be unfamiliar with how Julia expects types to be organized, but I thought it was silly that it can't recognize that a subtype (explicitly a subtype) of a supertype isn't a subtype of the supertype.
Is this an actual problem or am I just expected to keep these definitions in the same module? The organization doesn't work well for what I am doing, and I'd prefer another solution.
# Module A
module Definition
export State
abstract type State end
end
# Module B
module Subtype
include("Definition.jl")
using .Definition
export transiting
struct transiting <: State
from::Vector{<:Real}
to::Vector{<:Real}
end
end
# Module C
module Func
include("Definition.jl")
using .Definition
export foo
function foo(op::State)::State
return op
end
end
# Module Main
module Example
# include("Subtype.jl")
include("Func.jl")
include("Subtype.jl")
using .Func, .Subtype
t = transiting([0.0, 0.0, 0.0], [1.0, 0.0, 0.0])
println(isa(t, State)) # false
foo(t) # error
println("Complete")
end
You're getting a MethodError
, not a TypeError
. Your code doesn't run; you've missed include("Definition.jl")
and using .Definition
in your Example.jl
, so that State
is defined.
This behavior is expected. You should post (and look at) the full error:
julia> false
ERROR: MethodError: no method matching foo(::Main.Example.Subtype.transiting)
Closest candidates are:
foo(::Main.Example.Func.Definition.State)
@ Main.Example ~/so/func.jl:7
It tells you that the method definition is foo(::Main.Example.Func.Definition.State)
. If you do typeof(t)
you'll get Main.Example.Subtype.transiting
. If you now do supertype(typeof(t))
you'll get Main.Example.Subtype.Definition.State
. So you have a method that expects a ::Main.Example.Func.Definition.State
and you are passing a subtype of ::Main.Example.Subtype.Definition.State
instead. Note that the crucial difference lies in Func.Definition.State
versus Subtype.Definition.State
.
To avoid getting the namespaces mixed up, follow these two simple rules:
using
: using .SubModule1
, using .SubModule2
, etc.using ..MainModule: TypeA, method_a
Working code:
example.jl
:
module Example
include("definition.jl")
using .Definition
include("func.jl")
using .Func
include("subtype.jl")
using .Subtype
include("main.jl")
export main
end
definition.jl
:
module Definition
export State
abstract type State end
end
func.jl
:
module Func
using ..Example: State
export foo
function foo(op::State)::State
return op
end
end
subtype.jl
:
module Subtype
using ..Example: State
export transiting
struct transiting <: State
from::Vector{<:Real}
to::Vector{<:Real}
end
end
A few more things:
CamelCase
for struct names: Transiting
instead of transiting
, so that everyone using the code will know that Transiting
is a type instead of a functionCapitalized
names, while the others have lowercase
namesinclude
d in the main module definition, and you can manage what is in which namespace with using
/import
, again in the main module definition.using X
; prefer using X: XTypeA, XTypeB, xmethod_a, xmethod_b
instead
struct Transiting{T<:Real} <: State
from::Vector{T}
to::Vector{T}
end
Simpler (working) example:
Example.jl
:
module Example
include("definition.jl")
include("func.jl")
include("subtype.jl")
include("main.jl")
export main
end
definition.jl
:
abstract type State end
func.jl
:
function foo(op::State)::State
return op
end
subtype.jl
:
struct Transiting{T<:Real} <: State
from::Vector{T}
to::Vector{T}
end
main.jl
:
function main()
t = Transiting([0.0, 0.0, 0.0], [1.0, 0.0, 0.0])
println(isa(t, State))
foo(t)
end
use in the REPL by executing:
julia> using .Example
julia> main()
true
Main.Example.Transiting{Float64}([0.0, 0.0, 0.0], [1.0, 0.0, 0.0])
P.S. I'd suggest you edit the title of your question.