I would love to know your opinion, whether the (pretty safe) type-piracy as described below is in your opinion OK, and whether this mechanism via Base.convert() could become a “best-practice” example to solve the issue of glueing two packages together? Maybe it can even be supported by some convenience macros in the future?
Often a package FunctionProvider provides a certain functionality (e.g. a function) that are potentially useful if combined with another package MyStructProvider that hosts a particular datatype. However, the makers of FunctionProvider may not be aware of MyStructProvider and MyStructProvider may not be interested in importing FunctionProvider, since this may be a heavy package. The common compromise is for FunctionProvider to also provide a second lightweight package, which is then imported by MyStructProvider to allow writing the necessary glue code (e.g. rewrap the type) to make functions of FunctionProvider work with the datatype of MyStructProvider.
However, even this lightweight package needs maintenance and MyStructProvider may not want to include even this lightweight package since only few users may need this particular package combination.
Therefor I looked for a mechanism with a little bit of (fairly safe) type piracy that may provide a solution:
FunctionProvider.jl:
module FunctionProvider
export foo
function foo(something::Int)
print(something)
end
# here comes the code allowing "legalized type piracy".
TransferType = NamedTuple{(:FunctionProvider, :transfer), Tuple{Nothing, NamedTuple{(:data,), Tuple{Int64}}}}
function foo(something)
something = convert(TransferType,something)
foo(something.transfer.data)
end
end
Note that FunctionProvider.jl does NOT know about MyPackageProvider
MyPackageProvider.jl:
module MyStructProvider # does NOT import FunctionProvider and does not need FunctionProvider in its Dependencies
export StructProviderType
export convert
struct StructProviderType
mydata::Float64
end
using Base
TransferType = NamedTuple{(:FunctionProvider, :transfer), Tuple{Nothing, NamedTuple{(:data,), Tuple{Int64}}}}
function Base.convert(::Type{TransferType}, something::StructProviderType)::TransferType
converted = round(Int, something.mydata)
return (FunctionProvider=nothing, transfer=(data=converted,))
end
end
And here is an example, which demonstrates the use of this mechanism:
push!(LOAD_PATH, pwd()) # just to avoid adding the packages to the environment
using FunctionProvider
using MyStructProvider
a = StructProviderType(22.2)
foo(a)
Please don’t forget to change directory into the bare directory, where these 3 files are located.
Note that this glue, i.e. defining the Base.convert() function, can also be provided from outside both of these packages. What is the actual use-case? It would be nice, if my View5D.jl package provides an easy-to-implement mechanism that other datatype packages can include their meta information on units, axenames, pixelsize, offset etc. and choose default values (e.g. gamma) for direct visualization via the View5D.jl macros e.g. @vv myLocatedArray.
Much looking forward to your opinion. Why do I call it “pretty safe”? Well, the type that is converted to has the destination package name “FunctionProvider” in its type (without requiring an import) and both FunctionProvider and MyStructProvider know about this use.