Developing a package: hacking the include order of files

I am developing a package, let’s say Package.jl. In its scr directory, I have three files: A.jl and B.jl in which are defined three functions (a(), b() and c()), and the main Package.jl file.

The latter includes:

module Psycho

export
    a, c  # b is not exported

include("A.jl")
include("B.jl")
end

In summary, three functions (a, b, c) located in two files (A and B). Critically, both c() and a() use b(), which is unexported, and c() also uses b():
a → b
c → a & b

Here’s the twist. I would like c() and b() to be in the B.jl file, and a() to be in the A.jl file.

It seems that the above implementation doesn’t work (it errors and says that b() is not defined). It works if I place b() in A.jl before the definition of a(), but I would like to know what are my options to avoid that? I hope the example is clear enough.

Is including B.jl before A.jl an option?

That should work. Are you sure you’re not actually calling b() before it’s defined?

Sorry I got confused in my own example (I thought it’d be simpler but no :exploding_head:), I forgot to mention that c() also uses a() and b(), so including B before A doesn’t work as a() is not defined yet… I’ll edit the question

Are you sure a(), b() and c() are plain functions and not type definitions? You shouldn’t have any issues with functions since they are only resolved during first call which normally happens much later than the whole module is defined.

If these are indeed functions, could you please example content of A.jl and B.jl and other steps to reproduce it (e.g. ran from REPL, used precompilation, ran as script, etc.)?

4 Likes

The following works:

Psycho.jl:

module Psycho

export
    a, c  # b is not exported

include("A.jl")
include("B.jl")
end

A.jl:

a() = b() + 1

B.jl:

b() = rand()
c() = a() + b()
2 Likes

Yes you’re right! I simplified the code and indeed it works, sorry for this false alarm and thanks a lot :smile:

Just a follow up question — how about interdependent types? e.g.

A.jl:

struct A
	b :: B
end

B.jl:

struct B
	a :: A
end

It seems in this case the runtime will complain about type B being undefined when A.jl is included.

For interpreted languages, this behaviour is understandable; but I still wonder how one should declare/organise interdependent types?

Not quite optimal, but works in practice:

A.jl:

abstract type AbstractB end

struct A
    b::AbstractB
end

B.jl

struct B <: AbstractB
    a::A 
end 

Thanks for the example, though I agree that this setup is less ideal.

In my original example, I do wonder why Julia runtime needs to complain about not knowing type B during pre-compilation time — wouldn’t it be possible for Julia runtime to parse all the type definitions at once like other AOT-compiled languages?

Parametric types can also be used, and avoid the performance problems of abstract types as fields.

Anyway, this is a long-standing issue: handle mutually-circular type declarations · Issue #269 · JuliaLang/julia · GitHub

3 Likes