Shorthand for dispatch types

What do you think of providing a shorthand for creating types that are defined just for dispatching to relevant methods. For example (https://bkamins.github.io/julialang/2020/11/27/abt.html):

abstract type ABRule end
struct Greedy <: ABRule end
struct Thompson <: ABRule end
struct Unif <: ABRule end

or (JuMP.jl/src/print.jl)

abstract type PrintMode end
abstract type REPLMode <: PrintMode end
abstract type IJuliaMode <: PrintMode end

These two could be written down e.g. as:

structs Greedy | Thompson | Unif <: abstract type ABRule

and

abstract types REPLMode | IJuliaMode <: abstract type PrintMode

Here I used Haskell notation with | as delimiter. I guess a tuple is would be more fitting here. These shortcuts could also be provided by a macro. What do you think?

3 Likes

Yeah, I’ve thought about making a macro for this. Maybe call it @struct_enum

1 Like

It would probably make sense to use the same syntax as @enum, so you could write either

@struct_enum Fruit Apple Orange Kiwi

or

@struct_enum Fruit begin
    Apple
    Orange
    Kiwi
end
1 Like

Then maybe just piggyback on @enum

@enum struct Greedy Thompson Unif <: ABRule

and

@enum abstract type REPLMode IJuliaMode <: PrintMode

but also

@enum struct begin
    Greedy 
    Thompson
    Unif 
end <: ABRule

Are there any guidelines on reusing macros in different contexts?

I think there needs to be a way for distinguishing between abstract and concrete types as I can see people use both.

Generally it’s not a good idea to overload other people’s macros. The arguments to macros are usually of type Symbol or Expr (or literal values of type Int, String, etc). It’s difficult to use multiple dispatch for macros in an effective and unambiguous way (unless you have control over all of the methods yourself).

You can see here that the @enum macro only has one method defined.

I personally don’t think that a @struct_enum macro would need to support abstract types. Normally if you’re switching on a type, you’re using singletons that can be instantiated. For example, you might do something like this:

struct A end
struct B end

foo(x, ::A) = x + 1
foo(x, ::B) = x + 2

foo(1, A())
foo(1, B())

You can’t instantiate abstract types, so if you want to switch on abstract types, you would have to write it like this:

abstract type A end
abstract type B end

foo(x, ::Type{A}) = x + 1
foo(x, ::Type{B}) = x + 2

foo(1, A)
foo(2, B)

In my experience, singleton structs are usually more efficient than passing around the type objects themselves. In other words, A and B are objects of type DataType, whereas A() and B() are basically empty objects that are easy for the compiler to compile away.

2 Likes

For something like Rust enum types, you may find this GitHub - MasonProtter/SumTypes.jl: An implementation of Sum types in Julia helpful.

julia> @sum_type Fruit begin
           Apple()
           Banana()
           Orange()
       end

julia> Apple()
Fruit: Apple()

julia> Banana()
Fruit: Banana()

julia> Orange()
Fruit: Orange()

Not exactly what you’re looking for since these things are a closed set and require unwrapping, but perhaps still useful.