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)
    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”.

2 Likes