# Alternate argument types

Is there a macro or something that makes defining functions like this easier, where one or more of the arguments is of a given type?

``````struct MyType <: Number
x::Float64
end

f(a::MyType, b::MyType, c::MyType) = ...
f(a::MyType, b, c) = f(a, MyType(b), MyType(c))
f(a, b::MyType, c) = ...
f(a, b, c::MyType) = ...
# Now `f(a::MyType, b::MyType, c)` is ambiguous, so we have to define all the cross-versions as well
...
``````

If `f` is just my function, I can of course just do `f(args...) = f(MyType.(args)...)`, but if `f` is a function already defined for `Number` arguments, I donâ€™t want to override the default behaviour.

Since you refer to `Number` types, perhaps you are working with mathematical operations, in which case `promote` (and `promote_type`) is often used internally. You can define promotion rules that involve your type to make all of this happen automatically. Check out: Conversion and Promotion Â· The Julia Language

1 Like

Not quite. In this instance itâ€™s least squaresâ€™ fitting, and I need it to do other things beyond converting to the underlying numbers. Think

``````funtion f(a::MyType, b::MyType, c::MyType)
println("At least one of a, b or c is a MyType")
return f(a.x, b.x, c.x)
end
``````

.

``````"""
`` `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)
error("""
Usage: `@typeargs function f(a::A, b::B) where {any((A, B, C) .<: X)}...`
""")
end
return f
end
(signature, conds) = firstrest(f.args[1].args)
return f
end
(conds, common) = firstrest(conds)
if conds.args[1] === :any
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â€ť.

2 Likes