Julia equivalent of class methods?

I’m translating some Python code to Julia and am trying to figure out how to map the usage of class methods to Julia. See below for a Python-style outline of what I’m porting. There’s a single abstract class with one or more concrete subclasses. Each concrete subclass implements an action that is applied to a state (it’s code from a game simulation). One particular thing is that each subclass can generate actions of its type from the current game state, which is what generate() does. It returns a list of one or more actions that can later be applied to a state by calling execute() on the instance, passing the state to apply to.


class AbstractAction:
    def __init__(self, type, ...):
        # Hold some general fields
        self.type = type
class ConcreteActionA(AbstractAction):

    def generate(cls, state, ...):
        # Returns a list of one or more instances
        # of ConcreteAction possible in this state
        return [ConcreteActionA(...), ...]
    def __init__(self, ...):
        AbstractAction.__init__(self, ACTION_CONCRETE_A, ...)
        # Action specific fields
    def execute(self, state):
        # Apply the action to a state

state = <game state>
actions = ConcreteActionA.generate(state)
# Pick a random action and execute it
action = random.choice(actions)

Porting the class structure doesn’t seem to be too hard (apart from the composition versus forwarding choice), including the constructor and execute() method, something like this:

@enum ActionType

abstract type AbstractAction end

mutable struct ConcreteActionA <: AbstractAction
    # ...more action-specific fields

    function ConcreteActionA(...)
        obj = new(ACTION_CONCRETE_A)
        # Initialize other fields....
        return obj

function execute(action::ConcreteActionA, state::State)
    # ....

But what’s a good way to port generate()? Ideally I would be able to call

actions = generate(ConcreteActionA, state)

but I’m having trouble figuring out how to dispatch on a struct type (in contrast to a struct value), as I have many different forms of generate(), one for each action type. I looked into symbols to see if those could be usable here (e.g. :ConcreteAction), but I’m not clear whether that is a good method.

Any hints?

1 Like

I think you’re looking for the Type{...} singleton type. Illustrating the concept with an example

julia> struct Foo
          val :: Int

julia> foo = Foo(42)

julia> fun(x::Foo) = "dispatch on the type of a value"
fun (generic function with 1 method)

# This might be compared Python's class methods
julia> fun(::Type{Foo}) = "dispatch on a type itself"
fun (generic function with 2 methods)

julia> fun(foo)
"dispatch on the type of a value"

julia> fun(Foo)
"dispatch on a type itself"

Slight tangent, but I think the enum is superfluos. Multiple dispatch already handles execute(::ConcreteActionA, state) vs. execute(::ConcreteActionB, state), without the need to access any additional type field in that Action object :slight_smile:

If you’re looking for performance, you might even get away with having each action be immutable as well. You’d move the new to the end of the constructor and pass it all fields directly in that case. This is very useful if you don’t plan on modifying existing actions after they have been created (and even if you do, creating new immutable objects from existing ones (which is how you get modification on immutables) can usually be avoided by the compiler).


It seems to me that you want Val(:SymbolForAction)? This will increase the compilation time (but possibly improve the running times), and you will be able to dispatch in the actions.