How to reproduce the tk dialect?

Hello,
when using tk, you can initialize tk objects with a block, example:

TkLabel.new(root) {
text 'Hello, World!'
pack { padx 15 ; pady 15; side ‘left’ }
}

text, pack, padx, …, are used as commands instead of key of a hash. tk
objects allow to use a kind of ruby dialect, valid only in the context
of that object.

I would like to reproduce that technique but I don’t find how. I have
already tried some code:

class Dialect
def initialize
yield
end

def add_some_stuff(stuff)
@stuff = stuff
end

def show
p @stuff
end
end

Dialect.new {add_some_stuff(“Hello world!”); show}

It doesn’t work, the interpreter (1.8.0) saying add_some_stuff doesn’t
exist. I have also tried this:

class Dialect
def add_some_stuff(stuff)
@stuff = stuff
end

def show
p @stuff
end
end

var = Dialect.new
var.instance_eval {add_some_stuff(“Hello world!”); show}

It works but it uses an explicit call of instance_eval which I don’t
want to use. I have searched how to do the job the same way as with tk,
but I can’t find! Can anyone help please?

Shasckaw

shasckaw wrote:

Hello,
when using tk, you can initialize tk objects with a block, example:

TkLabel.new(root) {
text ‘Hello, World!’
pack { padx 15 ; pady 15; side ‘left’ }
}

Here’s how Tk might do it, though I haven’t studied the source. (I don’t
think there’s any way around using #instance_eval.)

class TkLabel
def initialize(obj, &block)
instance_eval(&block) if block
end
def text(str)
@text = str
end
def pack(&block)
@packing = Packer.new(&block)
end
attr_reader :packing
class Packer
def initialize(&block)
instance_eval(&block) if block
end
def padx(x)
@padx = x
end
def pady(y)
@pady = y
end
def side(str)
@side = str
end
end
end

root = nil

label = TkLabel.new(root) {
text ‘Hello, World!’
pack { padx 15 ; pady 15; side ‘left’ }
}

p label

There are some drawbacks:

  • inside the block, all private methods and instance variables are
    accessible

  • you can’t access from within the block attributes that are defined in
    the context outside the block:

    @x = 1
    pack {
    padx @x # this is a different @x
    }

    instead, however, you can use local variables to “pass in” the value:

    temp_x = @x = 1
    pack {
    padx temp_x
    }

An alternative is:

 class Packer
   def initialize(&block)
     block.call(self) # or use yield
   end
   ...
 end

which keeps the “self” context the same in the block, avoids exposing
internal stuff, and gives you an object (passed to the block) which you
can use to do the same methods (padx etc.). But it doesn’t look as
elegant…

pack { |packer|
packer.padx 15
}

There are times when I wish ruby had some kind of hygienic macro… but
then I think about reading/debugging code written in somebody’s personal
macrofied language…

Joel VanderWerf wrote:

shasckaw wrote:
Thanks for the help. I’ll try this the sooner I can.
There are times when I wish ruby had some kind of hygienic macro… but
then I think about reading/debugging code written in somebody’s personal
macrofied language…
In my researches on the net for a possible solution, I’ve read some
discussions with Matz about adding some kind of macro programming in
Rite, the next ruby interpreter version.

Shasckaw

Joel VanderWerf wrote:

shasckaw wrote:

Hello,
when using tk, you can initialize tk objects with a block, example:

TkLabel.new(root) {
text ‘Hello, World!’
pack { padx 15 ; pady 15; side ‘left’ }
}

Here’s how Tk might do it, though I haven’t studied the source. (I don’t
think there’s any way around using #instance_eval.)

[code sample]
Great, it works!
I have modified my own test this way:

class Dialect
def initialize(&block)
instance_eval(&block) if block
end

def add_some_stuff(stuff)
@stuff = stuff
end

def show
p @stuff
end
end

Dialect.new {add_some_stuff(“Hello world!”); show}

And it works.

There are some drawbacks:

  • inside the block, all private methods and instance variables are
    accessible
    I have found a workaround:

class Dialect
class Local
def add_some_stuff(stuff)
@stuff = stuff
end

 def show
   p @stuff
 end		

end

def initialize(&block)
Local.new.instance_eval(&block) if block
end
end

Dialect.new {add_some_stuff(“Hello world!”); show}

  • you can’t access from within the block attributes that are defined in
    the context outside the block:

    @x = 1
    pack {
    padx @x # this is a different @x
    }

    instead, however, you can use local variables to “pass in” the value:

    temp_x = @x = 1
    pack {
    padx temp_x
    }
    After some tests, I’ve realised that it is the same case for Tk code.
    Here is a sample:

require ‘tk’
class Atest
def initialize
@vartest = “Try this”
root = TkRoot.new { title “Ex1” }
TkLabel.new(root) {
text “|” + @vartest.to_s + “|”
pack { padx 15 ; pady 15; side ‘left’ }
}
end
end
Atest.new
Tk.mainloop

I’ve put a to_s because it crashes with ‘+’ not working on nil.

Until Rite adds those macro, I’ll make do with this technic. Thanks
again for your help.

Shasckaw