Nobody should quote me on this, but I think the pairwise or canonical-to-pairwise ordering takes care of the combinations interference issue.
IMO, the easiest resolution is for OP to give up on not wanting to have to write _f’s arguments in canonical order, whatever they choose that to be.
Hi, I’m new here and just stumbled upon this thread.
I come from the dotnet world so the solution that popped up in my head is more OOP like.
But if you don’t care too much about elegance and is willing to push some error into runtime, you can consider using something like a builder. I’ve never written julia code (I’m just a lurker) so hopefully what I write makes sense.
struct Builder
a::TypeA
b::TypeB
...
end
function _setA(builder::Builder, a)
function _setB(builder::Builder, b)
...
function run(builder::Builder) = f(a, b, ...) #throw if arguments are not set :(
Not clever or elegant, but at least you will get to control argument order until you find a more elegant solution. Feel free to correct me if this doesn’t work with type inference.
GitHub - Beforerr/PermuteArgs.jl: Generate multiple method definitions allowing arbitrary argument order based on types · GitHub may be of interest. I developed it for working with formula and it is general enough.
Cool, neat package! It seems like you implemented the method generator approach (n! scaling), so no canonical ordering? Any idea of the compilation overhead?
Yes. macro does a literal permutations(1:length(args)) and evals one concrete method per permutation so no @generated, no canonical order trick. I think JIT compilation is lazy so unused permutations are free until called (For Definition-time, noticeable at n=6 (720); n≥7 (5040+) would likely be painful or impractical.)
A lazy option for macro has been added. So now we can have constant definition time.
I might be late to the party, but have you considered making a method with typed keyword arguments and then mapping your positional arguments onto these keywords?
struct A x :: Int end
struct B x :: Int end
struct C x :: Int end
foo(;a::A, b::B, c::C) = begin #= your implementation here =# (a, b, c) end
foo(a::A, args...; kwargs...) = foo(args...; a, kwargs...)
foo(b::B, args...; kwargs...) = foo(args...; b, kwargs...)
foo(c::C, args...; kwargs...) = foo(args...; c, kwargs...)
println(foo(B(1), A(2), C(3)))
# -> (A(2), B(1), C(3))
println(foo(C(1), B(2), A(3)))
# -> (A(3), B(2), C(1))
println(foo |> methods |> length) # -> 4
Pros: Only linear amount of methods, somewhat simple (compared to some other suggestions)
Cons: Some boilerplate, each type in the signature must be unique (otherwise the problem becomes somewhat ill posed anyways).
If you do this regularly you could macroify the boilerplate away of course.
Edit: I could not sleep and ended up writing said macro myself. Hope it helps.
using MacroTools
macro orderless_dispatch(fndef::Expr)
# this macro deserves better error handling and generalization to other syntactic
# forms of function definition, but its a start
@capture fndef function fnname_(args__) body_ end
escargs = esc.(args)
escbody = esc(body)
escname = esc(fnname)
mappingmethod(e) = begin
@capture(e, arg_ :: T_) || throw("all arguments need to be typed")
:($escname($(esc(arg)) :: $(esc(T)), args...; kwargs...) = $escname(args...; $(esc(arg)), kwargs...))
end
return quote
function $escname(;$(escargs...)) $escbody end
$((mappingmethod(arg) for arg in args)...)
end
end
struct A x :: Int end
struct B x :: Int end
struct C x :: Int end
struct D x :: Int end
@orderless_dispatch function foo(a::A, b::B, c::C, d::D); (a, b, c, d) end
println(foo(D(1), C(2), A(3), B(4)))