Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: getting variable name [duplicate]

Tags:

ruby

How can I get a variable name? E.g.,

def get_var_name(var)
  # return variable name
end

myname = nil
get_var_name myname #=> myname

Initial purpose:

somevar = "value"

puti somevar #=> somevar = "value"
# that is a shortage for
# `puts "somevar = #{somevar.inspect}"`

My try:

def puti(symb)
  var_name  = symb.to_s
  var_value = eval(var_name)
  puts "#{var_name} = #{var_value.inspect}"
end
puti :@somevar # that actually will work only with class vars or whatever considering var scope;
like image 546
ted Avatar asked Mar 26 '13 13:03

ted


2 Answers

You need to carry across the binding of the current variable scope, which you do with the Binding class:

def puti(symb, the_binding)
  var_name  = symb.to_s
  var_value = eval(var_name, the_binding)
  puts "#{var_name} = #{var_value.inspect}"
end

somevar = 3

puti :somevar, binding   # Call the binding() method

   #=> outputs "somevar = 3"

The binding() method gives a Binding object which remembers the context at the point the method was called. You then pass a binding into eval(), and it evaluates the variable in that context.

like image 66
Jon Cairns Avatar answered Sep 20 '22 04:09

Jon Cairns


First, you cannot implement a puti and directly call puti a_var to get the output as a_var = value of a_var. At the body of puti, Ruby sees only the formal parameter names of puti, it cannot infer the actual parameter names.

In some other language like C/C++, you can use Macro to implement your puti. That's another story.

However, you can implement put :a_var, with the help of Continuation. In another question "Can you eval code in the context of a caller in Ruby?", Sony Santos has provided a caller_binding implementation to get the binding of the caller (something like the perl caller function).

The implementation should be altered a bit, because callcc returns the return value of the block at its first returning. So you'll get an instance of Continuation rather than nil. Here is the updated version:

require 'continuation' if RUBY_VERSION >= '1.9.0'

def caller_binding
  cc = nil     # must be present to work within lambda
  count = 0    # counter of returns

  set_trace_func lambda { |event, file, lineno, id, binding, klass|
    # First return gets to the caller of this method
    # (which already know its own binding).
    # Second return gets to the caller of the caller.
    # That's we want!
    if count == 2
      set_trace_func nil
      # Will return the binding to the callcc below.
      cc.call binding
    elsif event == "return"
      count += 1
    end
  }
  # First time it'll set the cc and return nil to the caller.
  # So it's important to the caller to return again
  # if it gets nil, then we get the second return.
  # Second time it'll return the binding.
  return callcc { |cont| cc = cont; nil }
end

# Example of use:

def puti *vars
  return unless bnd = caller_binding
  vars.each do |s|
    value = eval s.to_s, bnd
    puts "#{s} = #{value.inspect}"
  end
end

a = 1
b = 2
puti :a, :b
e = 1 # place holder...

# => a = 1
#    b = 2

Note the puti should not be the last statement of your program, otherwise the ruby interpreter will terminate immediately and the trace function has no chance to run. So that's the point of the last "place holder" line.

like image 2
Arie Xiao Avatar answered Sep 24 '22 04:09

Arie Xiao