Search code examples
crystal-lang

Seeking dynamic method dispatch via automated construction of Hash


I build stochastic simulation models. The state transitions are handled by event methods, but the order of occurrence varies depending on the randomness of the events so I need some form of dynamic dispatch. This has worked beautifully for me for over a decade in Ruby using send, but I’m attracted by the speed of Crystal so I’m trying to build a framework to build simulations in a Crystalline way.

The nature of the models is that event methods trigger specific subsets of the other events based on random conditions (and after random time delays but I have that covered), so the events really do need to be identified by a user-readable tag of some sort for scheduling purposes. I chose the approach of using a Hash that maps the event identifiers to their corresponding Procs, but want to separate the model logic from the event invocation as in the following “model” class and module. (Please be aware that the real code will be more complex than this, with event methods that schedule subsequent events with random delays via a priority queue. This is meant as an MCVE on just the dynamic-dispatch-by-name aspect of the problem.)

The Model:

require "./my_module"

class UserClass
  include MyModule

  def initialize
    @method_set = {
      :m1 => ->m1,
      :m2 => ->m2,
      :m3 => ->m3
    }
    p methods
  end

  def m1
    puts "In method1"
  end

  def m2
    puts "In method2"
  end

  def m3
    puts "In method3"
  end
end

UserClass.new.run(5)

Run control logic that's common to all models:

module MyModule
  # would like to replace this with a macro that
  # automates construction of the hash of procs.
  macro methods
    {{ @type.methods.map &.name.stringify }}
  end

  @method_set = {} of Symbol => Proc(Nil)

  def run(iterations)
    keys = @method_set.keys
    iterations.times { @method_set[keys[rand(keys.size)]].call }
  end
end

Now for the actual question. In serious models, there can be dozens or hundreds of event methods, so I’d like to use macros to automate construction of the Hash. I did some research and found how to get the method names via a macro, but can’t seem to find the right incantation to cough up a Symbol-driven Hash like the one I’m manually creating in the constructor. Any help or suggestions for alternate approaches would be appreciated.


Solution

  • Since you're using symbols as lookup keys, I assume you don't actually need to lookup methods by their name at runtime. It seems you may just need a way to reference methods.

    For that you can simply pass procs around, without any indirection through symbols:

    module MyModule
      macro methods
        [{{ @type.methods.map{|method| "-> #{method.name}"}.join(",").id }}]
      end
    
      def run(iterations)
        iterations.times { methods.sample.call }
      end
    end
    

    In case you need to be able to fully dynamically dispatch from a user-defined string like "m1" to UserClass#m1 (which wouldn't be possible with symbol keys, so I don't think you do), I'd simply match the names using a macro-generated case with direct method invocation, instead of hash lookup and procs.

    UPDATE: In case you need actual name resolution, you can use the following macro to generate a Hash mapping from method name to proc:

    module MyModule
      macro methods
        {
          {% for method in @type.methods %}
            {{method.name.stringify}} => -> {{method.name}},
          {% end %}
        }
      end
    end