[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

any way to manipulate variables and bindings?

Its Me

12/2/2004 6:27:00 PM

def foo
bar [:a, :b, :c]
a
b
c
end

I would like a, b, and c to be local variables with values 1, 2, 3
(positions of :a, :b, :c). Is there some implementation of
def bar (symbol_list)
symbol_list.each_with_index { |s, i|
???
}
end
that will do this?



5 Answers

Mark Hubbart

12/2/2004 6:58:00 PM

0

On Fri, 3 Dec 2004 03:27:45 +0900, itsme213 <itsme213@hotmail.com> wrote:
> def foo
> bar [:a, :b, :c]
> a
> b
> c
> end
>
> I would like a, b, and c to be local variables with values 1, 2, 3
> (positions of :a, :b, :c). Is there some implementation of
> def bar (symbol_list)
> symbol_list.each_with_index { |s, i|
> ???
> }
> end
> that will do this?

Not really. Technically, you could do something like this:

def bar(&block)
ary = block.call
ary.each_with_index{|s,i| eval("#{s}=#{i}", block.binding)}
end
==>nil
bar{[:a,:b,:c]}
==>[:a, :b, :c]
[a,b,c]
==>[0, 1, 2]


This would create local variables with those values. However, they
wouldn't be accessible in anything other than irb:

mark@eMac% ruby
def bar(&block)
ary = block.call
ary.each_with_index{|s,i| eval("#{s}=#{i}", block.binding)}
end
bar{[:a,:b,:c]}
p [a,b,c]
-:6: undefined local variable or method `a' for main:Object (NameError)
mark@eMac%

This is because ruby decides at _compile time_ whether an identifier
represents a variable or a method call. Since Ruby doesn't see
anything being assigned to a b and c at compile time, it assumes they
are methods, and raises an error.

You could hack around this by pre-defining your variables, but that's ugly.

mark@eMac% ruby
def bar(&block)
ary = block.call
ary.each_with_index{|s,i| eval("#{s}=#{i}", block.binding)}
end
a,b,c = *[]
bar{[:a,:b,:c]}
p [a,b,c]
[0, 1, 2]
mark@eMac%


HTH,
Mark


Florian Gross

12/2/2004 6:59:00 PM

0

itsme213 wrote:

> def foo
> bar [:a, :b, :c]
> a
> b
> c
> end
>
> I would like a, b, and c to be local variables with values 1, 2, 3
> (positions of :a, :b, :c). Is there some implementation of
> def bar (symbol_list)
> symbol_list.each_with_index { |s, i|
> ???
> }
> end
> that will do this?

You need the binding of the caller. Then you can just do eval("#{symbol}
= value", context) where context is the binding.

I have attached binding_of_caller.rb -- it provides Binding.of_caller
which yields the binding of the caller. See the documentation, it has to
be used in a relatively odd way:

def inc(name)
Binding.of_caller do |context|
eval("#{name} += 1")
end
end

x = 0; inc(:x); x # => 1

I have also attached variable.rb -- this provides variable boxing via
Variable[:foo] which creates an object for that variable. You can do
var.value = 5 and so on. Note that this does not yet work with instance
and class variables. It can be used like this:

def inc(variable)
variable.value += 1
end

x = 0; inc(Variable[:x]); x # => 1

It would also be interesting to know what you are using this for.

begin
require 'simplecc'
rescue LoadError
def Continuation.create(*args, &block)
cc = nil; result = callcc {|c| cc = c; block.call(cc) if block and args.empty?}
result ||= args
return *[cc, *result]
end
end

# This method returns the binding of the method that called your
# method. It will raise an Exception when you're not inside a method.
#
# It's used like this:
# def inc_counter(amount = 1)
# Binding.of_caller do |binding|
# # Create a lambda that will increase the variable 'counter'
# # in the caller of this method when called.
# inc = eval("lambda { |arg| counter += arg }", binding)
# # We can refer to amount from inside this block safely.
# inc.call(amount)
# end
# # No other statements can go here. Put them inside the block.
# end
# counter = 0
# 2.times { inc_counter }
# counter # => 2
#
# Binding.of_caller must be the last statement in the method.
# This means that you will have to put everything you want to
# do after the call to Binding.of_caller into the block of it.
# This should be no problem however, because Ruby has closures.
# If you don't do this an Exception will be raised. Because of
# the way that Binding.of_caller is implemented it has to be
# done this way.
def Binding.of_caller(&block)
old_critical = Thread.critical
Thread.critical = true
count = 0
cc, result, error, extra_data = Continuation.create(nil, nil)
error.call if error

tracer = lambda do |*args|
type, filename, context, extra_data = args[0], args[1], args[4], args
if type == "return"
count += 1
# First this method and then calling one will return --
# the trace event of the second event gets the context
# of the method which called the method that called this
# method.
if count == 2
# It would be nice if we could restore the trace_func
# that was set before we swapped in our own one, but
# this is impossible without overloading set_trace_func
# in current Ruby.
set_trace_func(nil)
cc.call(eval("binding", context), nil, extra_data)
end
elsif type != "line"
set_trace_func(nil)
error_msg = "Binding.of_caller used in non-method context or " +
"trailing statements of method using it aren't in the block."
cc.call(nil, lambda { raise(ArgumentError, error_msg) }, nil)
end
end

unless result
set_trace_func(tracer)
return nil
else
Thread.critical = old_critical
case block.arity
when 1 then yield(result)
else yield(result, extra_data)
end
end
end
require 'binding_of_caller'

class Variable
def self.new(name, context = nil)
return super(name, context) unless context.nil?
Binding.of_caller do |context|
super(name, context)
end
end
class << self; alias :[] :new; end

attr_reader :context, :name

def initialize(name, context)
unless /^[a-z_][a-zA-Z0-9_]*$/.match(name.to_s)
raise(NameError, "Illegal variable name: #{name.inspect}")
end

@name, @context = name, context
@setter = lambda do |value|
eval "#{@name} = ObjectSpace._id2ref(#{value.id})", context
end
exists = lambda do
eval "local_variables.include?(#{@name.to_s.inspect})", context
end
@getter = lambda do
eval("#{@name}", context) if exists.call
end
end

def []=(value); @setter.call(value); end
def []; @getter.call; end
alias :value= :[]=
alias :value :[]

def inspect
"Variable[#{@name.inspect}, #{@context.inspect}]"
end
end

Robert Klemme

12/3/2004 12:40:00 PM

0


"itsme213" <itsme213@hotmail.com> schrieb im Newsbeitrag
news:I7Jrd.100267$jq5.22882@fe2.texas.rr.com...
> def foo
> bar [:a, :b, :c]
> a
> b
> c
> end
>
> I would like a, b, and c to be local variables with values 1, 2, 3
> (positions of :a, :b, :c). Is there some implementation of
> def bar (symbol_list)
> symbol_list.each_with_index { |s, i|
> ???
> }
> end
> that will do this?

As Mark pointed out, you can't get this to work because of the compile
time decisions Ruby takes. You can either do this

def bar(*a) 0...a.size end

def foo
a,b,c = *bar(:a,:b,:c)
puts a,b,c
end

or this

def bar2(b,*a)
a.each_with_index {|s,i| eval("#{s}=#{i}",b)}
end

def foo2
bind = binding
bar2(bind, :a,:b,:c)
[:a,:b,:c].each do |sym|
puts eval(sym.to_s, bind)
end
end

which is really clumsy.

Kind regards

robert

Its Me

12/3/2004 4:41:00 PM

0


"Florian Gross" <flgr@ccan.de> wrote
> You need the binding of the caller. Then you can just do eval("#{symbol}
> = value", context) where context is the binding.
> ...
> ...<stuff i won't pretend to understand but will attempt to use!>
> ...
> It would also be interesting to know what you are using this for.

I'm exploring some domain-specific language capabilities in Ruby. One of the
things I want to do appears doable by generating local variables and/or
methods in the context of the caller.


Mark Hubbart

12/4/2004 2:03:00 AM

0

On Fri, 3 Dec 2004 21:42:47 +0900, Robert Klemme <bob.news@gmx.net> wrote:
>
> "itsme213" <itsme213@hotmail.com> schrieb im Newsbeitrag
> news:I7Jrd.100267$jq5.22882@fe2.texas.rr.com...
>
>
> > def foo
> > bar [:a, :b, :c]
> > a
> > b
> > c
> > end
> >
> > I would like a, b, and c to be local variables with values 1, 2, 3
> > (positions of :a, :b, :c). Is there some implementation of
> > def bar (symbol_list)
> > symbol_list.each_with_index { |s, i|
> > ???
> > }
> > end
> > that will do this?
>
> As Mark pointed out, you can't get this to work because of the compile
> time decisions Ruby takes.

I should clarify: Ruby will set the variables, they are just
inaccessible unless you assign to them at some point in that binding's
context:

mark@eMac% ruby
eval "a = 23"
p a
^D
-:2: undefined local variable or method `a' for main:Object (NameError)

mark@eMac% ruby
eval "a = 23"
a += 19
p a
^D
42

So, as long as you assign to them anywhere in that context, it's okay.
even if it's after they have been set once. head.spin(:rapidly)

I think the reason for this is that there was a tradeoff made: to do
what you want to do would require that variables be checked at
runtime, not eval-time. This would prevent early detection of errors
related to variable names. I guess it was decided that the error
detection was more important than a slightly more flexible/dynamic
local variable setup.

cheers,
Mark