Suppose I have a parameterized type Foo{T}
with a field val::T
, and an abstract type Bar
. I would like to:
# Library Code
struct Foo{T}
val::T
end
abstract type Bar end
baz(bar::Bar, a::Foo, b::Foo) = baz(bar, a.val, b.val)
# User Code
struct Bar1 <: Bar end
baz(bar::Bar1, a, b) = ...
This results in ambiguities because of multiple dispatch, but I would like my first method baz
to call the second baz
after unwrapping the Foo
s. The first baz
and the abstract type Bar
would be library code, and the second baz
and Bar1
would be user code. However if I call baz
with types (Bar1, Foo, Foo)
it is ambiguous what I am calling.
A solution would be to define a baz(bar::Bar1, a::Foo, b::Foo)
but like I said the first baz
is library code and the type Foo
is not exposed so this could only be done in the library code which can’t be done since the user is defining Bar1
.
It seems another solution would be to define the second baz
instead (not real code) like:
baz(bar::Bar1, a::Any-Foo, b::Any-Foo) = ...
where the syntax Any-Foo
means any type that is not a Foo
. That would resolve any ambiguities and the library could expose an alias for Any-Foo
to be used in its place. Does such a concept exist? And if not what is the best way for me to solve my problem?
I’m not quite sure I get the use case, since these two parts seem inconsistent:
The user can’t call baz
with the types (Bar1, Foo, Foo)
if the type Foo
is not exposed.
And if they call it with the types (Bar, S, T)
, where S
and T
are not Foo
, then there is no ambiguity, and your method for baz
doesn’t come into play at all.
The library code calls baz
with types (Bar1, Foo, Foo)
. The user defined the concrete type Bar1
which the library uses, only knowing that the user has provided some concrete implementation of Bar
.
The user calls some other code in the library with a Bar1
and some other stuff. The library later creates Foo
s that wrap that other stuff, and then calls baz
with (Bar1, Foo, Foo)
. I want that library call to resolve to the library definition which should then call the user definition after unwrapping to provide generic and extensible code.
In that case, I would probably just use a different function within the library, i.e.,
function _baz(bar::Bar, a::Foo, b::Foo) # internal and only called from library code
baz(bar, a.val, b.val) # part of the external interface and extendable by user code
end
Are there any reasons, why this would not work in your case?
I don’t want to do that because in this case my baz
function is actually Base.Order.lt(o:Base.Order.Ordering, a, b)
. I could have the user define _lt
(can’t do it in the library because it has to be lt
there), but I’d rather use the built in mechanism for ordering.
The current solution on my mind is to expose a macro that defines a baz(bar::Bar1, a::Foo, b::Foo)
with the unwrapping behavior when the user defines @custom_baz baz(bar::Bar1, a, b)
. That way Foo
is still not exposed to the user. I don’t love this solution either though and feel there should be some other way if you have any other suggestions?
Just for completeness I am posting the macro I am talking about here:
macro custom_order(lt_func)
ordering_type = lt_func.args[1].args[2].args[1]
unwrap_node_lt_func = quote
Base.Order.lt(
o::$ordering_type,
a::MergedIterators.MergedIteratorStateNode,
b::MergedIterators.MergedIteratorStateNode
) = Base.Order.lt(o, a.value, b.value)
end
quote
$(esc(lt_func))
$(esc(unwrap_node_lt_func))
end
end
Here lt_func
is the user defined baz
. unwrap_node_lt_func
is an auto-defined method to remove ambiguity. It ends up being defined by the user through the use of the library provided macro. MergedIterator.MergedIteratorStateNode
is Foo
. And finally ordering_type
is Bar1
.
Example use would be:
struct AbsOrder <: Base.Order.Ordering end
@custom_order Base.Order.lt(::AbsOrder, a, b) = Base.Order.lt(Base.Order.Forward, abs(a), abs(b))
Ok, that makes it more difficult. Guess that the following should work though:
# Library Code
struct Foo{T}
val::T
end
abstract type Bar end
_baz(bar::Bar, a::Foo, b::Foo) = _baz(bar, a.val, b.val)
# Library code always calls _baz, but forwards it to baz if no more special version is available
_baz(x, a, b) = baz(x, a, b)
# User Code
struct Bar1 <: Bar end
# User extends baz, i.e., no the dispatch within the library (on _baz) is ever in conflict with user methods
baz(bar::Bar1, a, b) = ...