[ANN] InstanceDispatch.jl: Dispatch-on-value with anyting that defines a `Base.instances` method

Hi there,
I just started the registration process for InstanceDispatch.jl, a single-macro package that writes for you a method that dispatches on methods with value-type from a type that defines a Base.instances method (typically an @enum). Say you have a function that behaves differently depending on a flag that has been passed to it (typically a finite state machine). For example

@enum GreetEnum Hello Goodbye

function greet(::Val{Hello}, who)
    return "Hello " * who
end
function greet(::Val{Goodbye}, who)
    return "Goodbye " * who
end

In your state machine you would like to store the flag as an instance of the enum, but then simply calling greet(Val(state), somename) is not type stable. This package automates the tedious task of writing and maintaining the following method:

function greet(e::GreetEnum, who)
    if e == Hello
        return greet(Val(Hello), who)
    elseif e == Goodbye
        return greet(Val(Goodbye), who)
    else
    end
end

Instead you can simply write:

@instancedispatch greet(::GreetEnum, who)

There’s also the possibility of dispatching on multiple enums, you can check the documentation!

7 Likes

This reminds me of ValSplit.jl

2 Likes

Oh! indeed, I did not find this package when I was looking for a solution to this problem. This is essentially the same thing for symbol-based value types. I will add a link to ValSplit,jl in the README file.

1 Like

Both InstanceDispatch.jl and ValSplit.jl are useful. I want something very similar, but I don’t think they work for me. This is what I am doing:

julia> struct Obj{S} end;

julia> Obj(s::Symbol) = Obj{s}();

julia> Base.:*(::Obj{:A}, n) = n * 2;

julia> Base.:*(::Obj{:B}, n) = n * 3;

julia> Obj(:A) * 2
4

julia> Obj(:B) * 2
6

I could write methods for Base.:* for ::Val{<:Symbol}. But this would be type piracy. Furthermore, I have additional semantics for Obj{S}. I don’t want to confuse this with Val.

Maybe something like ValSplit that allows you to specify the type in the macro (or some macro). Following the example in ValSplit, instead of

@valsplit function soundof(Val(animal::Symbol)) ...

you would have

@valsplit Obj function soundof(Obj(animal::Symbol)) ...

EDIT: I bet no one here is smart enough to modify ValSplit.jl to do this. Yeah, a waste of time mentioning it.

heh, JK, I am considering looking into it.

Oh well, that turned out to be an interesting problem. I just pushed a fix to InstanceDispatch.jl that allows using dotted function names. As soon as this patch lands in Julia’s registry, you can solve your problem by doing something like:

julia> using InstanceDispatch

julia> @enum ObjLabel A B

julia> struct Obj
           label::ObjLabel
       end

julia> Base.:*(::Val{A}, n) = n * 2

julia> Base.:*(::Val{B}, n) = n * 3

julia> @instancedispatch (Base.:*)(::ObjLabel, n)

julia> Base.:*(o::Obj, n) = Base.:*(o.label, n)

julia> Obj(A) * 2
4

julia> Obj(B) * 2
6

julia> # Or, if you prefer the type annotation solution
       struct Obj2{Label} end

julia> Obj2(l::ObjLabel) = Obj2{l}()
Obj2

julia> Base.:*(::Obj2{Label}, n) where {Label} = Base.:*(Label, n)

julia> Obj2(A) * 2
4

julia> Obj2(B) * 2
6

As for modifying ValSplit.jl, if I recall correctly the code is not too complicated; you probably want to generalize it by tweaking valarg_params to add an argument with the object type that replaces Val.

1 Like

Still interesting since ValSplit.jl doesn’t seem to work on Julia 1.12 :pensive_face:

That was shameless nerdsniping, on my part. :wink: I am eager to try this

2 Likes

If that makes the package better I have no problem with nerd-sniping. :slight_smile: