How to reproduce the tk dialect?

S

shasckaw

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
 
J

Joel VanderWerf

shasckaw said:
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...
 
S

shasckaw

Joel said:
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
 
S

shasckaw

Joel said:
shasckaw said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,578
Members
45,052
Latest member
LucyCarper

Latest Threads

Top