How to conditionally add new features

Lets say i have a package with a type and methods

module Core
    abstract type AbstractInterfaceType end

    foo(a::AbstractType) = println("do interface stuff")

    struct MyBaseType <: AbstractInterfaceType end

    bar(a::MyBaseType) = println("do base stuff")
end

Now i want to add a new type that a user might use, but it comes with a heavy dependency.

import SomeHeavyPackage

struct MyExtType <: AbstractInterfaceType end

bar(a::MyExtType) = println("do ext stuff")
baz(a::MyExtType) = println("do more ext stuff")

How to do this correctly?

function __init__()
    @require SomeHeavyPackage="blablablabla" include("MyExtType.jl")
end

You can declare the functions in the main package and then implement them in the extension package.

In Core.jl

function bar end
function baz end

In the extension:

import SomeHeavyPackage
import Core: bar, baz

struct MyExtType <: AbstractInterfaceType end

bar(a::MyExtType) = println("do ext stuff")
baz(a::MyExtType) = println("do more ext stuff")
4 Likes

Requires technically allows names conditioned on multiple loaded packages, but extensions deliberately moved away from that messiness. Extensions aren’t much accessible and can’t be listed in import statements, and it’s ambiguous and disallowed to do using Core, SomeHeavyPackage: MyExtType. Your intuition is right, exposed names deserve an importable namespace, save extensions for extensions.

1 Like

But is the new type accessable?

Yes because it’s part of your main package. You just can’t do some things with it until the extension defining appropriate methods is loaded

1 Like

I think he might be asking if the type’s name MyExtType would be accessible from outside the extension, and it’s not in the way mkitti structured it. You can get at the type in various ways (get_extension is the most general) but not import the name. The type could be defined in either of the packages instead of the extension, just unimplemented besides the constructors.

1 Like

Depending on the setup, it could also make sense to add something like

# Core
export get_interface_implementation
get_interface_implementation(pkg) = get_interface_implementation(Val(pkg))
get_interface_implementation(::Val{M}) = error("No interface extension implemented for $M")

# SomeHeavyPackage extension to Core
import SomeHeavyPackage
struct MyExtType <: AbstractInterfaceType end
get_interface_implementation(::Val{SomeHeavyPackage}) = MyExtType

That is, you could define get_interface_implementation in Core and add methods in each extension in order to provide access to the types. So if the user does using Core, SomeHeavyPackage then they can do T = get_interface_implementation(SomeHeavyPackage) to get the type.

1 Like

Oh yes my bad, I didn’t even look in detail. Since extensions are hard to access, the usual recommendation would be to define everything that external users need inside of the main package, both types and functions, hence my hasty answer.