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.