Parameterize all types excluding a certain type

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 Foos. 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 Foos 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) = ...