How to dispatch by value?

It’s fair to ask for the intended use case. Here it is. It’s a language interpreter.

struct Expr
    id, data
end

id is a hundred or so symbols, say :integer, :string, :+, :-, :*, :confused: …that is a hundred or so language “lexicons” (I meant to type :slash but it rendered as an frowning icon)

data can be very simple if it is atomic like a number: Expr(:number, 123.4)

or can be recursive, e.g. for addition we will have Expr(:+, )

Expr(:+, (Expr(:integer, 1), Expr(:integer, 2)))

Now to display an Expr as a string we may define a string function:

function Base.string(x::Expr)
    if x.id == :integer 
        string(x.data)
    elseif x.id == :something_else
        do_something_else(x.data)
    elseif ..
    ... hundreds of these...
end

I am no longer opposed to doing

Expr_number <: Expr
Expr_add <: Expr 
Expr_subtract <: Expr
...
Base.string(x::Expr_number) = ...
Base.string(x::Expr_add) = ...
Base.string(x::Expr_subtract) = ...

It’s nice this way each operation can be kept in its own file.

Is it worthwhile to have a few hundred subtypes as opposed to one Expr where id tells me the operation? To avoid a hundred if…then branches?

As the conditions seem to be mutually exclusive, you can try to define a global to build the resulting function AST, then modify it within other files by adding more branches and finally eval it.

Edit:
Or just define a dispatch table as a Dict{Symbol, Any}?
Then it will be

p(c::Structure) = DISPATCH_TABLE[c.id](c.data)
1 Like

Posted another topic to understand the combinatorial explosion issue:

Thx

The thing you’re trying to do here is basically what often goes under the name “traits” in Julia. There’s several different packages which facilitate this. Under the hood they basically do something equivalent to @xiaodai’s Val code above, so there’s no getting around the performance issues being discussed, but if performance is not critical, traits can certainly offer a nice syntax / logical organization / allow you or your users to add new cases in a way that would be impossible with a big if-statement.

For example, using https://github.com/schlichtanders/Traits.jl you can do exactly your original desired syntax:

using Traits

struct Structure
    id
end

@traits function p(c::Structure) where {c.id == :x}
    "c.id is x"
end
@traits function p(c::Structure) where {c.id == :y}
    "c.id is y"
end

julia> p(Structure(:x))
"c.id is x"

julia> p(Structure(:y))
"c.id is y"

julia> p(Structure(:z))
ERROR: MethodError: no method matching p(...)

The nice thing is that the final thing is a MethodError as you might expect, and you’re free to implement it later if you’d like.

3 Likes

Amazing. Thanks

2 Likes

Despite the manual warning against over-use of types with values as parameters, it is worth noting (for others searching for this topic) that Julia itself does exactly that for 7 different ways of rounding:

Presumably the benefit there of dispatch over if/elseif is the extensibility.
It’s a clean example for anyone wanting to essentially dispatch on a few Symbols.

2 Likes