Import types "above" a module with ..: for consistency and brevity?

Hi @ffevotte. Good question. Here’s a simple example that captures the essence of a real problem we’re having at my company: Suppose we have a MyTypes module that exports a bunch of common types. Those types are on the interface for functions in the Subsystem module. Now we want to build two separate systems that use Subsystem. Here’s an example. Suppose the modules are included from separate files (they’re just written this way so there’s a single working example file).

Here’s a system that needs to use the functionality of Subsystem directly.

module SystemA

    # Included from MyTypes.jl.
    module MyTypes
        export Type1, Type2
        struct Type1; field::Int64; end
        struct Type2; field::Float64; end
    end

    using .MyTypes

    # Included from Subsystem.jl.
    module Subsystem
        export f
        import ..MyTypes: Type1, Type2 # This line breaks modularity!
        # import ..SystemA: Type1, Type2
        f(a::Type1, b::Type2) = Type2(a.field + b.field)
    end

    using .Subsystem

    # Now for whatever this module was supposed to do.
    x = f(Type1(1), Type2(2.0))

end

Here’s a different system, one that needs to use the functionality of Subsystem indirectly. [Edit: This is intended to be totally separate from SystemA, like a different program.]

module SystemB

    # Included from MyTypes.jl.
    module MyTypes
        export Type1, Type2
        struct Type1; field::Int64; end
        struct Type2; field::Float64; end
    end

    using .MyTypes

    # Included from SomeOtherSubsystem.jl.
    module SomeOtherSubsystem

        export g
        import ..MyTypes: Type1, Type2
        # import ..SystemA: Type1, Type2

        # Included from Subsystem.jl.
        module Subsystem
            export f
            import ..MyTypes: Type1, Type2 # This line breaks modularity!
            # import ..SystemA: Type1, Type2
            f(a::Type1, b::Type2) = Type2(a.field + b.field)
        end

        using .Subsystem

        g(a, b) = f(Type1(b.field), Type2(a.field))

    end

    using .SomeOtherSubsystem

    # Now for whatever this module was supposed to do.
    x = g(Type1(1), Type2(2.0))

end

SystemA.x
SystemB.x

This doesn’t work. In order for Subsystem to work in the second case, its import statement would need an extra period, because it’s deeper here. Hence no single Subsystem.jl file works in both cases.

If the import ..: Type1, Type2 syntax worked, then this type of modularity would work.

This is all part of a bigger picture that looks like this: My company needs all of this code in a single repo. We want modularity. Solutions:

  • If we break things into modules like this, modules actually can’t be used in multiple contexts, so this doesn’t work.
  • If we break things into local packages and ]dev them, then there are new problems with modularity ["Unsatisfiable requirements" when local packages use local packages (fixed in v1.4)].
  • If we create a local registry for our own package, that’s also problematic [ibid].
  • This only working solution we’ve found is to avoid making modules and packages and just make a bunch of .jl files with functions and types in them, and then include all those files in one big module (possibly Main). This obviously has its own problems, like name conflicts and the fact that nothing can be precompiled, etc., so the startup time is now really slow.

That’s all just context to answer your question, François. This thread should still be about whether or not ..: is useful and should be prioritized.