I am trying to build linked structures, which refer to each other, or to nothing/null, as you would do with pointers in C. In order to do this I am using abstract types and parametric structs in order to break the dependency recursion (as seen on Stack Overflow).
At the same time I am trying to use modules to logically group these data structures with their respective functions. I have put the abstract types in their own module and now I am running into problems. see below for a minimum working example.
Questions:
Is there a more idiomatic way to implement pointers than Union{Type1,Nothing}?
How can I do “using abstract_stuff” in multiple places and make julia understand that I am referring to the same abstract types?
Cheers,
K
#abstractstuff.jl
module abstract_stuff
export abstract_husband, abstract_wife
abstract type abstract_husband end
abstract type abstract_wife end
end
#REPL
module A
export husband
include("abstractstuff.jl")
using .abstract_stuff
mutable struct husband{W<:abstract_wife}<:abstract_husband
name::String
my_wife::Union{W,Nothing}
end
end
using .A
include("abstractstuff.jl")
using .abstract_stuff
mutable struct wife{H <: abstract_husband}<:abstract_wife
name::String
my_husband::Union{H,Nothing}
end
h=husband{wife}("bob",wife{husband}("cindy",nothing))
h.my_wife.my_husband = h
Result:
h=husband{wife}("bob",wife{husband}("cindy",nothing))
ERROR: TypeError: in husband, in W, expected W<:Main.A.abstract_stuff.abstract_wife, got Type{wife}
Stacktrace:
[1] top-level scope
@ REPL[6]:1
Generally this kind of linked list may not be the most efficient for Julia. It is hard to suggest something very specific without more context, but I would use something like this:
using ArgCheck
mutable struct Male
name::String
wife_index::Int # 0: single
end
mutable struct Female
name::String
husband_index::Int # 0: single
end
men = [Male(name, 0) for name in men_names]
women = [Female(name, 0) for name in women_names]
function marry!(men, women, husband_index, wife_index)
male = men[husband_index]
female = woman[wife_index]
@argcheck male.wife_index == 0
@argcheck female.husband_index == 0
male.wife_index = wife_index
female.husband_index = husband_index
end
I was hoping for automatic garbage collection. Using array indices as a hand rolled addressing scheme seems to force me to do my own memory management, doesn’t it?
No. It is the more idiomatic solution for maybe-null pointers. If it was a tree, for example, you could also just use a Vector of nodes instead (with the empty Vector playing the role of nothing). The alternative for your case is your type itself having some object that represents the null value. You can create a struct NoWife <: abstract_wife; end and use the only object of this type (i.e., NoWife()) as sentinel for when there is no wife but then I do not see a great advantage over Union{T, Nothing}.
The problem you are having comes from the fact that, in Julia, you should never be requireing the same code at two or more places. You have some alternatives:
Create a package for abstract_stuff, Pkg.develop it, and then just call using PackageName without any require.
Hack LOAD_PATH to include the folder where abstractstuff.jl is inside then, just call using ModuleName without any require.
In your case, in which you are using the REPL, you should probably not be includeing the abstractstuff.jl inside the REPL anyway. You should access it from the module A, like A.abstract_stuff.abstract_wife., without needing to re-include anything.