Composite pattern and DSL design

Hello,

I am currently writing a little DSL for gui framework (Ruiby, for gtk).
As my DSL become more and more complex, I have some design issue.

For declaring a drawing area, with event handler, I use

canvas(w,h,{
mouse_down: proc { |w,e| … },
mouse_up: proc { |w,e| … },
mouse_move: proc { |w,e| … },
expose: proc { |w,gc| … }
})

But the list of handler becoming bigger, I wonder if this form should be
better :
canvas w,h do
on_mouse_up do |e|

end
on_mouse_down do |e|

end

end

The issue is that in this form, user can put other commands
than on_xxx in the block.
So if it put, by example, a button, I can’t control what will append:
canvas w,h do
on_mouse_up do |e|

end
button “hello” do alert(“coucou”) end
end

so the framework will become hard to use : very common mistake will
produce exotic bug.

So here my question:
Implementing object composition by cascade of closures, how to control
the ‘syntax’ of the composition?

(sorry for my english… )

Regis d’Aubarede wrote:

So here my question:
Implementing object composition by cascade of closures, how to control
the ‘syntax’ of the composition?

The same way you yield events to your callbacks, you should yield some
“context” object to the other blocks. The most common object would
probably be self so you can append to it:

class GUI::Canvas
  def initialize(width, height)
    @width, @height = width, height
    yield self
  end
end

class GUI::Button
  def initialize(width, height)
    @width, @height = width, height
    yield self
  end
end

canvas w, h do |c|
  c.on_mouse_up do |e|
  end
  c.button "hello" do |b|
    b.on_mouse_up do |e|
      c.alert("coucou")
    end
  end
end

Only the methods that are available on the context object will work, and
it’s explicit about which context is being appended so there are no
exotic bugs.

Look at XML::Builder (either the original 1 or nokogiri 2) for
examples of this pattern.

You can also use instance_eval if you don’t want to yield the context
to the block. (Or instance_exec if you still need to pass parameters
to the block.) But this will change the scope of the block so you can’t
access any of the outer closures unless you yield it in:

class GUI::Canvas
  def initialize(width, height, &block)
    @width, @height = width, height
    instance_exec(self, &block)
  end
end

class GUI::Button
  def initialize(width, height)
    @width, @height = width, height
    instance_exec(self, &block)
  end
end

canvas w, h do |c|
  # with instance_eval, self is the canvas
  on_mouse_up do |e|
  end
  c.button "hello" do |b|
    # with instance_eval, self is the button
    on_mouse_up |e| do
      # error: with instance_eval, `c` is not available here:
      c.alert("coucou")
    end
  end
end

Andrew V.

Andrew V. wrote in post #1131541:

Regis d’Aubarede wrote:

So here my question:
Implementing object composition by cascade of closures, how to control
the ‘syntax’ of the composition?

The same way you yield events to your callbacks, you should yield some
“context” object to the other blocks. The most common object would

Thank you, that work.
but to try to avoid instance_eval and yielding parent object
to each children (it’s a little tyring for user).
for that i use a @stack variable.

==============================
ex. :
vbox {
button ‘e’ { }
hbox {
canvas 22,33
button ‘a’ { action }
}
}

implementation:

====================================
def vbox
w= Gtk::VBox.new
@stack << w
yield
@stack.pop!
@stack.last.pack w
w
end

def button(r,&blk)
@stack.last.authorize? :button # the solution?
w=Gtk:Button.new(t)
w.signal(‘click’) { &blk.call } if &blk
@stack.last.pack w
w
end

so the solution should be to stack a dummy object before
canvas yield :

====================================
def canvas(w,h,&blk)
@stack.last.authorize? :canvas
w= Drawing.new(w,h)
@stack << NoPackClass.new
yield
@stack.pop
end

def button(r,&blk)
@stack.last.authorize? :button

end

====================================

So @stack.last has role of parent in the composition.

On Tue, Dec 24, 2013 at 3:38 PM, Regis d’Aubarede [email protected]
wrote:

mouse_up: proc { |w,e| },
on_mouse_down do |e|

end
button hello do alert(coucou) end
end

You can easily avoid that by providing a proxy instance to the block
which will only accept #on_xyz methods and forward them.

class EventProxy < BasicObject
def initialize(owner)
@owner = owner
end

def method_missing(sym, *args, &block)
if /\Aon_/ =~ sym
@owner.send(sym, *args, &block)
else
super
end
end
end

def canvas(&b)
Canvas.new.tap do |canvas|
# …
EventProxy.new(canvas).instance_eval(&block)
# …
end
end

Note: a bit more error checking and logic may be in order, e.g. to
convert between the proxy and the proxied object for method arguments
and return values.

so the framework will become hard to use : very common mistake will
produce exotic bug.

I am not entirely sure that this is true. Why is it necessary to
first define event listeners before further composing the UI? That
may be quite a restriction. In fact I would create the framework in a
way that I could add and remove listeners at will at any time in the
widget’s life cycle.

So here my question:
Implementing object composition by cascade of closures, how to control
the syntax of the composition?

See above.

Kind regards

robert

On Thu, Dec 26, 2013 at 4:31 PM, Robert K.
[email protected]wrote:

canvas(w,h,{

canvas w,h do
def initialize(owner)
end
convert between the proxy and the proxied object for method arguments

Nice to hear from you, Robert. :slight_smile:

On Fri, Dec 27, 2013 at 2:29 AM, tamouse pontiki
[email protected] wrote:

Nice to hear from you, Robert. :slight_smile:

:slight_smile:

I should have mentioned that there is another way to reach the goal:
the widget class could store an internal state indicating that it is
under construction. During that time other methods than defining event
listeners could throw. Still, I am not sure it is such a good idea to
be so restrictive.

Kind regards

robert

Robert K. wrote in post #1131685:

the widget class could store an internal state indicating that it is
under construction. During that time other methods than defining event
listeners could throw. Still, I am not sure it is such a good idea to
be so restrictive

yes,this is my solution : this avoid the instance_éval on an object
which is unknown by the user.