I have a class hierarchy and I want to be able to call a specific ancestor’s implementation of a method as I traverse the hierarchy. I am using super calls to move up the hierarchy, but then I want to be able to call the current @type’s implementation of another virtual method.
Here’s my example
class MachineState
def on_entry
# empty
end
def on_entry_recurse(old_state : MachineState)
# empty
end
macro inherited
# Entry starts below the top common state and progresses down
def on_entry_recurse(old_state : MachineState)
# stop once we reach a common ancestor
if old_state.class > {{@type}}
super
on_entry # calls virtual method - not what we want
# maybe something like: {{@type}}::on_entry ??
end
end
end
end
class S1 < MachineState
def on_entry
puts "In S1"
end
end
class S11 < S1
def on_entry
puts "In S11"
end
end
class S111 < S11
def on_entry
puts "In S111"
end
end
describe "test it" do
it "rescurses through ancestors" do
s1 = S1.new
s111 = S111.new
s111.on_entry_recurse(s1)
# WANTED: In S11\n In S111
# ACTUAL: In S111\nIn S111
end
end
class S1
def my_method
puts "S1"
end
end
class S11 < S1
def my_method
puts "S11"
S1::my_method # What is the proper syntax to call S1::my_method without using super
end
end
I don’t this is something that is possible in any OOP language. You can’t just skip superclasses to call a specific one as it would break encapsulation. I’d consider thinking if you really need this, or could design things differently so you don’t.
At the least you could define dedicated protected methods in the hierarchy to do that but probably is a better way.
Use case is described in initial post. Hierarchical state machine. I want to be able to define states with simple on_entry and on_exit methods. These should be called based on the transition between states. To do that, I need to recurse through the structure of states calling on_entry and on_exit as needed.
This could be solved with a macro, but I am learning Crystal and want to understand what is possible.
I would like to separate the traversal (kinda ugly and should be defined once) from the functionality.
If I use super, I have to put the traversal and its logic in every state’s on_entry and on_exit functions.
I would also need to define on_entry and on_exit for each state even if they do not need it. If I separate them the call from the traversal, I can have a default empty implementation of on_entry and on_exit in the base class that does nothing.
I’m not sure I see the problem, having super on the first line of each method is probably a lot more readable/easy to maintain than some complex macro to abstract that one word.
EDIT:
In those cases just don’t call super? NVM, misread
To be clear if you define them on the base type, then children only need to define them/call superif they need to do something specifically. Otherwise they would ofc inherit the behavior of the base type, i.e. noop.
It is more than just a super call. I need to check to see if we should stop the traversal. We stop at common ancestors. Further, even if one state does not define on_exit or on_entry, any parent might.
I’ll post the macro if I continue down this path. :D
If you only want to define the traversal once on a particular superclass, why not make it a different method that isn’t obscured by subtypes?
class S1
def my_method_S1
puts "S1"
end
def my_method
my_method_S1
end
end
class S11 < S1
def my_method
puts "S11"
my_method_S1
end
end
In general, though, I agree that you should consider whether accessing a particular superclass implementation of an obscured method is really what you want.