So I need a DAG for a project but I’m really struggling to wrap MetaDiGraph
in a wrapper struct to ensure that it is in fact a DAG. I eventually gave up and just left it untyped but this has been really bothering ever since, since I’m sure that it must be possible somehow.
I managed to correctly (I assume) define the struct itself
struct DAG{V, D, _F}
inner::MetaGraphsNext.MetaDiGraph{Int, V, Graphs.SimpleDiGraph{Int}, D, Nothing, Nothing, _F, Float64}
function DAG(graph::MetaGraphsNext.MetaDiGraph{Int, V, Graphs.SimpleDiGraph{Int}, D, Nothing, Nothing, _F, Float64}) where {V, D, _F}
@assert Graphs.is_directed(graph)
@assert !Graphs.is_cyclic(graph)
new{V, D, _F}(graph)
end
end
which works fine when used like this
julia> DAG(MetaGraphsNext.MetaGraph(Graphs.DiGraph(), Label = String, VertexData = Symbol))
DAG{String, Symbol, MetaGraphsNext.var"#3#5"}(Meta graph based on a {0, 0} directed simple Int64 graph with vertex labels of type String, vertex metadata of type Symbol, edge metadata of type Nothing, graph metadata given by nothing, and default weight 1.0)
but from there I’m running into two problems.
The first one is that I want to define a zero-argument constructor that I can use to avoid having to specify the full MetaGraph
constructor each time. I tried this in a few different ways but the two that felt closest to being correct were
DAG(::Type{V}, ::Type{D}) where {V, D} = let f = x -> 1.0
DAG{V, D, typeof(f)}(MetaGraphsNext.MetaGraph(Graphs.DiGraph(), Label = V, VertexData = D, weight_function = f))
end
and:
DAG(V}, ::Type{D}) where {V, D} = let f = x -> 1.0
DAG{V, D, typeof(f)}(MetaGraphsNext.MetaGraph(Graphs.DiGraph(), Label = V, VertexData = D, weight_function = f))
end
Unfortunately neither of these work…
julia> DAG(String, Symbol)
ERROR: MethodError: no method matching DAG{String, Symbol, var"#1#2"}(::MetaGraphsNext.MetaDiGraph{Int64, String, Graphs.SimpleGraphs.SimpleDiGraph{Int64}, Symbol, Nothing, Nothing, var"#1#2", Float64})
Stacktrace:
[1] DAG(#unused#::Type{String}, #unused#::Type{Symbol})
@ Main ~/a/path/to/project/dag.jl:33
[2] top-level scope
@ REPL[1]:1
julia> DAG{String, Symbol}()
ERROR: MethodError: no method matching DAG{String, Symbol, var"#1#2"}(::MetaGraphsNext.MetaDiGraph{Int64, String, Graphs.SimpleGraphs.SimpleDiGraph{Int64}, Symbol, Nothing, Nothing, var"#1#2", Float64})
Stacktrace:
[1] (DAG{String, Symbol})()
@ Main ~/a/path/to/project/dag.jl:33
[2] top-level scope
@ REPL[1]:1
The other problem that I’m facing is that I can’t create a type alias for DAG
to avoid having to specify the types each time. Normally I’d do something like this
const SpecificDAG = DAG{String, Symbol}
but if I do that then I can’t build the type anymore
julia> StringSymbolDAG(MetaGraphsNext.MetaGraph(Graphs.DiGraph(), Label = String, VertexData = Symbol))
ERROR: MethodError: no method matching (StringSymbolDAG)(::MetaGraphsNext.MetaDiGraph{Int64, String, Graphs.SimpleGraphs.SimpleDiGraph{Int64}, Symbol, Nothing, Nothing, MetaGraphsNext.var"#3#5", Float64})
Stacktrace:
[1] top-level scope
@ REPL[2]:1
which is not what happens if I do a similar thing for Dict
(as an example)
julia> const SpecificDict = Dict{String, Symbol}
Dict{String, Symbol}
julia> SpecificDict()
Dict{String, Symbol}()
julia> SpecificDict("a" => :a)
Dict{String, Symbol} with 1 entry:
"a" => :a
I feel like there’s just something about parametric types that I just don’t understand, but one step at a time I guess. Can anyone help me out here?