Multiple nested module inclusion

Hi there,

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?

This would be a simpler scheme…

julia> mutable struct Person
         name::String
         married_to::Union{Person,Nothing}
       end

julia> bob = Person("Bob",nothing)
Person("Bob", nothing)

julia> cindy = Person("Cindy",nothing)
Person("Cindy", nothing)

julia> function marry(a,b)
         a.married_to = b
         b.married_to = a
       end
marry (generic function with 1 method)

julia> marry(bob,cindy)
Person("Bob", Person("Cindy", Person(#= circular reference @-2 =#)))

julia> bob
Person("Bob", Person("Cindy", Person(#= circular reference @-2 =#)))

julia> cindy
Person("Cindy", Person("Bob", Person(#= circular reference @-2 =#)))


But it is possible that this circular reference brings issues which I do not immediately see. Do they?

1 Like

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:

  1. Create a package for abstract_stuff, Pkg.develop it, and then just call using PackageName without any require.
  2. Hack LOAD_PATH to include the folder where abstractstuff.jl is inside then, just call using ModuleName without any require.
  3. 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.
2 Likes

Yes, if you want GC you can stick to your original scheme.

1 Like