Working with abstract classes and their descendants, is it possible to check for the existence of an instance variable or method at runtime ?
Maybe depending on your use case. Can you share some example code/expand on what you need this for?
In Crystal, methods that are not called may be removed by the compiler. So, if you try to check if a method exists at runtime, it might act like something from quantum physics. A method that should not be there might show up just because you looked for it. (Just a joke)
Context …
abstract class Tool
@canvas : DrawingCanvas
def initialize(@canvas)
end
abstract def apply(x : UInt32, y : UInt32)
end
class Eraser < Tool
def apply(x : UInt32, y : UInt32)
# erase @canvas at x,y
end
end
class Pen < Tool
property color : UInt32 = 0
def apply(x : UInt32, y : UInt32)
# set @canvas x,y to @color
end
end
canvas = Canvas.new
pen = Pen.new(canvas)
eraser = Eraser.new(canvas)
tool : Tool = pen
# This works
if tool.is_a?(Pen)
tool.color = 0xFF0000FF
end
# This doesn't ; solution?
if tool.has_a?("color")
tool.color = 0xFF0000FF
end
You have a few options, the easiest would be to leverage Object.responds_to?
:
if tool.responds_to?(:color=)
tool.color = 0xFF0000FF
end
However this has some limitations in that it doesn’t really do anything more than assert that the provided method exists. Also can’t provide the method name via a variable, has to be known at comp time. The more robust way to handle this would be use modules as interfaces and check against that. E.g.
module Colorable
abstract def color : UInt32
abstract def color=(color : UInt32) : Nil
end
class Pen < Tool
include Colorable
property color : UInt32 = 0
# ...
end
if tool.is_a?(Colorable)
tool.color = 0xFF0000FF
end
Works similarly to your .is_a?(Pen)
check, but more focused and could be included in any type that has color. Can also use it for overloading purposes and such as well.
Modules for interfaces … nice; that’s even better. I’ll look it up. Thanks !
No problem! One think I think that’s worth clarifying tho is neither of these options are technically runtime things. So you still retain type safety. If you wanted something like obj.has_def?(some_runtime_var)
then that would be a different story.
Right. Thanks for the clarification.
Final code :
module ColorHolder
abstract def color : UInt32
abstract def color=(color : UInt32) : Nil
end
class DrawingCanvas
end
abstract class Tool
@canvas : DrawingCanvas
def initialize(@canvas)
end
abstract def apply(x : UInt32, y : UInt32)
end
class Eraser < Tool
def apply(x : UInt32, y : UInt32)
end
end
class Pen < Tool
include ColorHolder
property color : UInt32 = 0
def color=(color : UInt32) : Nil
@color = color
puts "color changed."
end
def apply(x : UInt32, y : UInt32)
# set @canvas x,y to @color
end
end
canvas = DrawingCanvas.new
pen = Pen.new(canvas)
eraser = Eraser.new(canvas)
tool : Tool = pen
if tool.is_a?(ColorHolder)
tool.color = 0xFF0000FF. # color changed.
end