Passing method as Proc to other class constuctor?

This works but is ->(w : Widget) { upd_lighttable(w) } (in MyAppUI.assemble) idiomatic?

abstract class Widget
  abstract def update
  abstract def render
end

abstract class UI
  def initialize()
    @widgets = Array(Widget).new
    assemble
  end

  abstract def assemble

  def update()
    @widgets.each do |w|
      w.update
    end
  end

  def render()
    @widgets.each do |w|
      w.render
    end
  end
end

class LabelWidget < Widget
  def initialize(@text : String, @x : Int32, @y : Int32, @handler : Proc(Widget, Nil))
  end

  def update()
    @handler.call(self)
  end

  def render()
    puts "todo: #{@text} LabelWidget.render"
  end
end

class MyAppUI < UI
  def assemble()
    @widgets.push(LabelWidget.new(" L ", 5,  5, ->(w : Widget) { upd_lighttable(w) }))
    @widgets.push(LabelWidget.new(" P ", 5, 50, ->(w : Widget) { upd_play_from_previous(w) }))
  end

  def upd_lighttable(w : Widget)
    puts "todo: upd_lighttable"
  end

  def upd_play_from_previous(w : Widget)
    puts "todo: upd_play_from_previous"
  end
end




ui = MyAppUI.new
ui.update
ui.render
# =>
# todo: upd_lighttable
# todo: upd_play_from_previous
# todo:  L  LabelWidget.render
# todo:  P  LabelWidget.render

In Ruby and Crystal, it’s common to use a block when a method takes one function argument.
It’s just a convention — your Proc version is fine too.

1 Like

Also you could skip the explicit proc literal and use a method pointer instead:
->(w : Widget) { upd_lighttable(w) }) could be written as ->upd_lighttable(Widget).

And, as mentioned in the previous comment, you could turn the proc argument into a block parameter. This would allow calling the method with a block (which you don’t need to use here with the method pointers, but it’s still neat to have for other cases).

Simply add a & before @handler in the def parameters and before ->:

class LabelWidget < Widget
  def initialize(@text : String, @x : Int32, @y : Int32, &@handler : Proc(Widget, Nil))
  end
end

# ...

class MyAppUI < UI
  def assemble()
    @widgets.push(LabelWidget.new(" L ", 5,  5, &->upd_lighttable(Widget))
    @widgets.push(LabelWidget.new(" P ", 5, 50, &->upd_play_from_previous(Widget))
  end
end
1 Like

TIL!

Revisiting this … Why does the following code fail to compile?

def initialize(@x, @y, &@handler : Proc(Widget, Nil), &@updater : Proc(Widget, Nil))
  ...
end
def initialize(@x, @y, &@handler : Proc(Widget, Nil), &@updater : Proc(Widget, Nil))
                                                    ^
Error: expecting token ')', not ','

I read some more … seems one cannot declare more than one block for a method’s arguments.

Refactoring to this and updating client code :

def initialize(@window, @renderer, @font, @x, @y, @w, @h, @text, @updater : Proc(Widget, Nil), @handler : Proc(Widget, Nil))
1 Like