"""
`` `julia
@orthomethods function f(a::A, b::B, c::C) where {any((A, B, C) .<: T || Y)}
# function definition here
end
` ``
Create orthogonal methods for all combinations,
```A<:T, B<:T, C<:T```
```A<:T, B<:T, C<:Y```
```A<:T, B<:Y, C<:Y```
...
(Except all `Y`)
If `|| Y` is left out, `Any` is used.
Can also generate the method for
```A<:T, B<:T, C<:T```
by way of
```julia
@orthomethods function f(a::A, b::B, c::C) where {all((A, B, C) .<: T)}
end
` ``
Will leave the signature intact and won't affect further subtyping, but the `any` or `all`
must be the first one in the `where` list.
"""
macro orthomethods(f)
return esc(orthomethodshelper(f))
end
function orthomethodshelper(f::Expr)
if f.head !== :function
error("""
Usage: `@typeargs function f(a::A, b::B) where {any((A, B, C) .<: X)}...`
""")
end
if f.args[1].head !== :where
return f
end
(signature, conds) = firstrest(f.args[1].args)
if first(conds).head !== :call
return f
end
(conds, common) = firstrest(conds)
if conds.args[1] === :any
if conds.args[2].head === :||
default = conds.args[2].args[2]
conds = conds.args[2].args[1]
else
default = :Any
conds = conds.args[2]
end
out = Expr(:block)
typenames = conds.args[2].args # [:A, :B, :C]
s = subsets(typenames) # [], [:A], [:A, :B], [:A, :B, :C], [:B], [:B, :C], [:C]
for k in s
isempty(k) && continue
push!(out.args, Expr(:function, Expr(:where, signature,
[Expr(:<:, n, n in k ? conds.args[3] : default) for n in typenames]...,
common...
),
f.args[2]
)
)
end
return out
end
if conds.args[1] === :all && conds.args[2].args[1] === :.<:
typenames = conds.args[2].args[2].args # [:A, :B. :C]
return Expr(:function, Expr(:where, signature,
[Expr(:<:, n, conds.args[2].args[3]) for n in typenames]...,
common...
),
f.args[2]
)
end
error("First type spec should be `any((A,B,C) .<: X)` or `all((A,B,C) .<: X)`")
end
Edit edit: This is pretty much perfect. Might package later.
And I think the word I was looking for was “Orthogonal methods”.