Traits and type piracy

TLDR: I’m defining a trait in a meta package A and trying to assign the trait in a package B that imports A. This causes type piracy and I don’t know how to resolve it.

Background: Inspired by other examples such as MLJBase, I’ve recently added a meta package to our Taija ecosystem: TaijaBase. I want to use this package to define common functionality, types and traits that are used by multiple packages in our ecosystem.

Specific Issue: TaijaBase (package A) currently defines a trait that can be used to determine whether at function can be parallelized using TaijaParallel (package B) or not. By default, all objects are defined as NotParallel:

"A base type for a style of process."
abstract type ProcessStyle end

"By default all types have this trait."
struct NotParallel <: ProcessStyle end
ProcessStyle(::Type) = NotParallel()

"Processes that can be parallelized have this trait."
struct IsParallel <: ProcessStyle end

# Implementing trait behaviour:
parallelizable(x::T) where {T} = parallelizable(ProcessStyle(T), x)
parallelizable(::IsParallel, x) = true
parallelizable(::NotParallel, x) = false

In TaijaParallel, I am trying to overload the ProcessStyle function like so:

import TaijaBase

"The `generate_counterfactual` method is parallelizable."
TaijaBase.ProcessStyle(
    ::Type{<:typeof(CounterfactualExplanations.generate_counterfactual)},
) = TaijaBase.IsParallel()

"The `evaluate` function is parallelizable."
function TaijaBase.ProcessStyle(
    ::Type{<:typeof(CounterfactualExplanations.Evaluation.evaluate)},
)
    return TaijaBase.IsParallel()
end

According to Aqua.jl, this amounts to type piracy:

julia> using TaijaParallel, Test

julia> using Aqua
       
       @testset "Aqua.jl" begin
           # Ambiguities needs to be tested seperately until the bug in Aqua package (https://github.com/JuliaTesting/Aqua.jl/issues/77) is fixed
           Aqua.test_ambiguities([TaijaParallel]; recursive = false, broken = false)
       
           Aqua.test_all(TaijaParallel; ambiguities = false)
       end
┌ Warning: Package cuDNN not found in current path.
│ - Run `import Pkg; Pkg.add("cuDNN")` to install the cuDNN package, then restart julia.
│ - If cuDNN is not installed, some Flux functionalities will not be available when running on the GPU.
└ @ FluxCUDAExt ~/.julia/packages/Flux/Wz6D4/ext/FluxCUDAExt/FluxCUDAExt.jl:57
┌ Warning: Package cuDNN not found in current path.
│ - Run `import Pkg; Pkg.add("cuDNN")` to install the cuDNN package, then restart julia.
│ - If cuDNN is not installed, some Flux functionalities will not be available when running on the GPU.
└ @ FluxCUDAExt ~/.julia/packages/Flux/Wz6D4/ext/FluxCUDAExt/FluxCUDAExt.jl:57
Possible type-piracy detected:
[1] TaijaBase.ProcessStyle(::Type{<:typeof(CounterfactualExplanations.generate_counterfactual)}) @ TaijaParallel ~/code/TaijaParallel.jl/src/CounterfactualExplanations.jl/assign_traits.jl:4
[2] TaijaBase.ProcessStyle(::Type{<:typeof(CounterfactualExplanations.Evaluation.evaluate)}) @ TaijaParallel ~/code/TaijaParallel.jl/src/CounterfactualExplanations.jl/assign_traits.jl:9
Piracy: Test Failed at /Users/paltmeyer/.julia/packages/Aqua/OTNBC/src/piracies.jl:240
  Expression: isempty(v)
   Evaluated: isempty(Method[TaijaBase.ProcessStyle(::Type{<:typeof(CounterfactualExplanations.generate_counterfactual)}) @ TaijaParallel ~/code/TaijaParallel.jl/src/CounterfactualExplanations.jl/assign_traits.jl:4, TaijaBase.ProcessStyle(::Type{<:typeof(CounterfactualExplanations.Evaluation.evaluate)}) @ TaijaParallel ~/code/TaijaParallel.jl/src/CounterfactualExplanations.jl/assign_traits.jl:9])

Stacktrace:
 [1] macro expansion
   @ ~/.julia/juliaup/julia-1.10.2+0.aarch64.apple.darwin14/share/julia/stdlib/v1.10/Test/src/Test.jl:672 [inlined]
 [2] test_piracies(m::Module; broken::Bool, kwargs::@Kwargs{})
   @ Aqua ~/.julia/packages/Aqua/OTNBC/src/piracies.jl:240
 [3] test_piracies
   @ ~/.julia/packages/Aqua/OTNBC/src/piracies.jl:217 [inlined]
 [4] macro expansion
   @ ~/.julia/packages/Aqua/OTNBC/src/Aqua.jl:100 [inlined]
 [5] macro expansion
   @ ~/.julia/juliaup/julia-1.10.2+0.aarch64.apple.darwin14/share/julia/stdlib/v1.10/Test/src/Test.jl:1577 [inlined]
 [6] test_all(testtarget::Module; ambiguities::Bool, unbound_args::Bool, undefined_exports::Bool, project_extras::Bool, stale_deps::Bool, deps_compat::Bool, piracies::Bool, persistent_tasks::Bool)
   @ Aqua ~/.julia/packages/Aqua/OTNBC/src/Aqua.jl:99
Test Summary:                                | Pass  Fail  Total   Time
Aqua.jl                                      |   10     1     11  18.2s
  Method ambiguity                           |              None   0.0s
  Unbound type parameters                    |    1            1   0.0s
  Undefined exports                          |    1            1   0.0s
  Compare Project.toml and test/Project.toml |    1            1   0.0s
  Stale dependencies                         |    1            1   3.2s
  Compat bounds                              |    4            4   0.1s
  Piracy                                     |          1      1   0.4s
  Persistent tasks                           |    1            1   9.4s
ERROR: Some tests did not pass: 10 passed, 1 failed, 0 errored, 0 broken.

How should I go about this? Any advice would be much appreciated!

Many thanks,

Pat

Aqua is right, because TaijaParallel owns

  • neither TaijaBase.ProcessStyle
  • nor CounterfactualExplanations.generate_counterfactual / CounterfactualExplanations.Evaluation.evaluate

Maybe a package extension to TaijaBase that depends on CounterfactualExplanations?

3 Likes

Since you are defining the trait yourself you could solve this by having a second (optional) context argument. You can see an example in GitHub - beacon-biosignals/StableHashTraits.jl: Compute hashes over any Julia object simply and reproducibly (read the section on customizing contexts)

1 Like

Ok, thanks both! I think I’ll go with the first solution by @gdalle but instead define this trait in TaijaParallel (where it probably belongs anyway). But adding a context argument is also an interesting idea, thank you very much.