How to check for existance of instance method?

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?

1 Like

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)

1 Like

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.

1 Like

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
1 Like