Stream: helpdesk (published)

Topic: Order agnostic dispatch


view this post on Zulip Lucien Huber (Sep 05 2022 at 12:54):

Hello!

Say I have many different concrete types and I want to specify a method that is dispatched on the set of types A,B,C,D all different, but in an order agnostic way, is there no better option than to specify f(::A,::B,::C,::D) and then specify all other possible combinations by then calling that method?

This would imply writing 4!=24 different method definitions...

view this post on Zulip Fredrik Ekre (Sep 05 2022 at 12:58):

You could define const ABCD = Union{A, B, C, D} and use f(a::ABCD, b::ABCD, c::ABCD, d::ABCD) and then do a runtime check of the times. I imagine you would have to sort the variables somehow internally anyway in this case?

view this post on Zulip Lucien Huber (Sep 05 2022 at 13:05):

No but in this case it would match a call to f(A,A,A,A) which I do not want, I only want to match when all are different

view this post on Zulip Sukera (Sep 05 2022 at 13:06):

you can do typeof(a) !== typeof(b) !== typeof(c) !== typeof(d) || throw(ArgumentError("types not distinct")) in your method

view this post on Zulip Fredrik Ekre (Sep 05 2022 at 13:06):

Yea, but you can check for that and error is what I meant.

view this post on Zulip Lucien Huber (Sep 05 2022 at 13:10):

Sukera said:

you can do typeof(a) !== typeof(b) !== typeof(c) !== typeof(d) || throw(ArgumentError("types not distinct")) in your method

or lets say I want something else to happen for f(A,A,A,A).. i.e definately not dispatch and throw an error

view this post on Zulip Fredrik Ekre (Sep 05 2022 at 13:11):

Then you define another method with that signature. It will be more specific than the union.

view this post on Zulip Lucien Huber (Sep 05 2022 at 13:12):

aha yes!

view this post on Zulip Lucien Huber (Sep 05 2022 at 13:12):

Thanks!

view this post on Zulip Lucien Huber (Sep 05 2022 at 17:41):

I've created a macro for this:

using MacroTools
using Combinatorics
function applyPerm(perm,vector)
  newVector=copy(vector)
  for (i,j) in enumerate(perm)
      newVector[i]=vector[j]
  end
  newVector
end


function invertPerm(perm,vector)
  newVector=copy(vector)
  for (i,j) in enumerate(perm)
      newVector[j]=vector[i]
  end
  newVector
end
invertPerm([2,3,1],applyPerm([2,3,1],[1,2,3]))
function combineargs(arguments)
  [combinearg(arg...) for arg in arguments]
end
def = :(
  function t(p,t,s::A,[a::A,b,c::B,d::D])
      @info "Abcd"
  end
)


macro symmetric(def)
dict=splitdef(def)
arguments=map(splitarg,dict[:args])


symargs=[]
indices=[]
nargs=0
newarguments=[]

for arg in arguments
  if isexpr((arg[1]),Symbol)
      push!(newarguments,arg)
      nargs+=1
  else
      addsymargs=(splitarg.(arg[1].args))
      push!(newarguments,addsymargs...)
      nnewsymargs =length(addsymargs)
      push!(indices,(nargs+1:nargs+nnewsymargs)...)
      nargs+=nnewsymargs
      push!(symargs,addsymargs...)
  end
end
arguments=newarguments
dict[:args]=combineargs(arguments)
argtypes=map(x->x[2],symargs)

perms=collect(multiset_permutations(argtypes,length(argtypes)))

n=length(argtypes)
m=length(perms)
MATCHED  = gensym()
intperms=Array{Int}(undef, m,n)
for (i,perm) in enumerate(perms)
for (j,type) in enumerate(argtypes)
  k  =findfirst(x -> x===type,perm)
  perm[k]=MATCHED
  intperms[i,j]=k
end
end

fns=Vector{Expr}(undef,length(intperms[:,1]))
fns[1]=esc(combinedef(dict))
for (i,perm) in enumerate(eachrow(intperms[2:end,:]))

  cdict=copy(dict)
  carguments = copy(arguments)
  carguments[indices]  = applyPerm(perm,arguments[indices])
  argu  =map(x->x[1],arguments)
  body=:($(dict[:name])($(argu...)))
  cdict[:body]=body
  cdict[:args]=combineargs(carguments)
  fns[i+1]=esc(MacroTools.combinedef(cdict))
end

return quote $(fns...) end
end
struct A end
struct B end
struct C end
struct D end


@symmetric function p([a::A,b,c::B,d::D])
    @info "Abcd"
end

p(A(),A(),C(),D())#method not found
p(A(),B(),D(),C())#method found!

However this is my first macro so I this I might have gotten some things wrong. When the method isn't found I get an error: could not determine location of method definition, Could someone more experienced take a look?

view this post on Zulip Jameson Nash (Sep 21 2022 at 11:19):

I think what you are looking for is diagonal dispatch:

view this post on Zulip Jameson Nash (Sep 21 2022 at 11:20):

f(a::T, b::T, c::T, d::T) where {T} = "all same"
f(a, b, c, d) = "not all same"

view this post on Zulip Lucien Huber (Sep 26 2022 at 08:51):

The thing I want to avoid is to have to define methods for all the different orderings of the types.. So while diagonal dispatch eliminates the all equal case, it doesn't really solve the problem. The macro above actually works quite well, except this weird error thing..

I guess what I want is to be able to dispatch on sets of types, not specific orders of types.


Last updated: Oct 02 2023 at 04:34 UTC