Ruby: Getting Variable Name

Ruby: getting variable name

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.

How to print variable's name?

Brief Summary

This answer originally recommended (and still recommends) a Symbol-based solution to the OP's question. However, it was recently expanded to include quoting as a conceptually simpler approach, as well as some minimal explanations about the internals of how Ruby variables are stored and referenced.

Original Recommendations

Print a Symbol

If you already know the name of the variable, you can simply print the variable's Symbol. Ruby will convert this to a String automagically for you when invoking #puts. For example:

asdf = Hash.new
puts :asdf

will print the following on standard output:

asdf

and returns nil, because Kernel#puts always returns nil. Use other methods like Kernel#p, Kernel#format, or some other method if you need to return the value in addition to sending it to an I/O stream.

Return a String

If you prefer to return the name of the variable instead of just printing it, then you need to convert the Symbol to a String. For example:

asdf = Hash.new
:asdf.to_s
#=> "asdf"

Some Additional Context, Seven Years Later

Quoting Variable Names to Print Strings

In your example, you could also have simply quoted the variables. For example, the following will all do more or less the same thing in slightly different ways because you're printing a String rather than a variable's value:

puts 'asdf'
puts "asdf"
puts %(asdf)
printf "%s\n" % "asdf"

However, I didn't originally suggest quoting because String interpolation, return values from methods, or other common use cases make it less ambiguous to use a Ruby Symbol for printing the variable's name as that's how Ruby stores it internally anyway.

For your given example, it wouldn't make any difference whether you used a Symbol or a quoted String, so quoting was probably the easiest and most straightforward answer that could have been given. On the other hand, using the variable's Symbol has fewer edge cases and allows for more advanced introspection and debugging of your code once you get beyond the basics.

Why the Symbol Approach Works

As I pointed out in a comment 7+ years after the original answer was given, all Ruby variables are stored in a Binding, Object, Module, or other scope as a Symbol anyway. Ruby then uses the Symbol to look up the available variables within the current scope, and then uses the Symbol to reference its value. So, not only can you inspect what local, instance, class, and global variables are defined in your current scope, you can also find out what kind of variables they are (e.g. "local-variable", "instance-variable", or "constant") with the defined? keyword.

The original answer was sufficient for the OP to make it the accepted answer. However, understanding why it works, how variables and symbols are related, and why Symbol-based solutions are recommended for use cases like this one may help other visitors in the future.

Ruby: Getting name of variable

It's not going to work.

There's no reliable way to retrieve the variable name an object is assigned to. Here's a contrived example:

def check_string(foo)
bar = foo
LibHelper::Base.check_type([bar], [String])
end

var = 123
check(var)

What's the correct error message in this case?

#=> Expected var to be type: String
#=> Expected foo to be type: String
#=> Expected bar to be type: String

Furthermore you can easily create objects that are not assigned to a variable:

LibHelper::Base.check_type([123], [String])

A better error message would be:

#=> Expected 123 to be type: String

i.e. just use variable.inspect

Ruby - print the variable name and then its value

Sure it is possible!

My solution tests the var by Object#object_id identity: http://codepad.org/V7TXRxmL

It's crippled in the binding passing style ...

Although it works just for local vars yet, it can be easily be made "universal" adding use of the other scope-variable-listing methods like instance_variables etc.

# the function must be defined in such a place 
# ... so as to "catch" the binding of the vars ... cheesy
# otherwise we're kinda stuck with the extra param on the caller
@_binding = binding
def write_pair(p, b = @_binding)
eval("
local_variables.each do |v|
if eval(v.to_s + \".object_id\") == " + p.object_id.to_s + "
puts v.to_s + ': ' + \"" + p.to_s + "\"
end
end
" , b)
end

# if the binding is an issue just do here:
# write_pair = lambda { |p| write_pair(p, binding) }

# just some test vars to make sure it works
username1 = "tyndall"
username = "tyndall"
username3 = "tyndall"

# the result:
write_pair(username)
# username: tyndall

Get The Name Of A Local Variable

foo = 1
bar = "42"
baz = Hash.new

%w(foo bar baz).each do |vn|
v = eval(vn)
puts "#{vn} = (#{v.class}) #{v}"
end

But this, of course, doesn't help you if you want a method with 1 argument.

Get Original Variable Name From Function in Ruby

Implementation of the idea from the comment, but I reiterate that it's extremely fragile and a really bad idea all around.

def orig_var_name(var)
loc = caller_locations.first
line = File.read(loc.path).lines[loc.lineno - 1]
line[/#{__method__}\(\s*(\w+)\s*\)/, 1]
rescue Errno::ENOENT
raise "Not usable from REPL"
end

foo = 1
puts orig_var_name(foo)
# => foo

Calling variable name of instance from within class

Unconventional method (solves the question you posed)

Okay. For the sake of posterity I'll put the code here on how to accomplish what you asked. Remember this is NOT the way the language was meant to be used. But if you get into meta-programming this will be very useful knowledge.

class Playlist

def display
puts "Your playlist name is #{name}"
end

private
def name
scope = ObjectSpace.each_object(Binding).to_a[-1]
scope.
local_variables.
select {|i| eval(i.to_s, scope) == self}.
map(&:to_s).delete_if {|i| i== "_"}.first
end

end

alternative = Playlist.new
# => #<Playlist:0x00000002caad08>
alternative.display
# Your playlist name is alternative

Details (how it works)

Alright let me explain the parts. ObjectSpace is where all objects get stored. You can see how many Objects exist by calling ObjectSpace.count_objects. The most useful feature, in my opinion, is the each_object method. With this method you can iterate over however many of any particular object which have been created. So for playlist you can call ObjectSpace.each_object(Playlist) and you get an Enumerable object. We can simply turn that into a list by appending .to_a on the end. But at this point you're getting the instances of Playlist in an Array like this: [#<Playlist:0x0000000926e540>, #<Playlist:0x000000092f4410>, #<Playlist:0x000000092f7d90>]. This is functional if you wanted to access them individually and perform some action. But this is not what you're trying to do since we don't have the instantiated variable name these instances are assigned to.

What we really want to call is the local_variables method and we want to call that in the main scope (not from within your classes scope). If you call local_variables from within your display method you get back an empty Array []. But if you call it in the main console after you've created an instance you would get back something like this [:alternative, :_]. Now we're talking! Now there's the issue of getting the scope from outside the class to be used within it. This was tricky to track down. Normally you could just pass in binding as a parameter, or even use TOPLEVEL_BINDING. But something I noticed showed me that these each create an instance of Binding that won't get updated any more. That means once you call TOPLEVEL_BINDING anything else you define, like another playlist, won't be updated and in your list of TOPLEVEL_BINDING.local_variables. This was a sad thing for me to find. But I discovered a way to solve this.

By calling ObjectSpace.each_object(Binding).to_a we now have a list of every binding instance. So we just need to know how to get the latest one that's up to date. After experimenting I found the last one will always be up to date. So we index by [-1]. Now we can call .local_variables on it and we will always get the latest collection of instance variables within the global scope. This is great! Now we just need to match the instance variable to the current Playlist that we're in. So we select from the global local_variables any that match the current instance. We need to call eval to get the instance, and with eval we need to tell it what scope to run in so we use select {|i| eval(i.to_s, scope) == self}. From there we take the symbols and map them to strings with .map(&:to_s) and lastly we have an extra item in our list we don't need. The underscore symbol is kind of a Ruby trick to get the last thing that was processed. So we'll need to remove it since it evaluated to the same id as our current variable instance did. So we do .delete_if {|i| i== "_"}. And lastly it's a list of one item, the thing we want, so we pick it out with .first

NOTE: This scope selecting method doesn't work in Rails. There are many bindings instantiated. The last one and the largest one with local_variables aren't the up to date ones.

This went through many unconventional means to accomplish the task you asked about. Now it may be you didn't know the standard way that something like naming a playlist class is done, and that's okay. No one knew at first, it is a learned trait.

Convential way to name a playlist

This is the preferred method for naming a playlist class.

class Playlist

def initialize(name)
@name = name
end

def display
puts "Your playlist name is #{@name}"
end

end

list = Playlist.new("Alternative")
list.display
# => "Your playlist name is Alternative"

This is rather straight forward. It's best to work with the way a language was designed to be used.

If I were you I would make list an Array of Playlist items and use it like this.

list = []
list << Playlist.new("Alternative")
list << Playlist.new("Rock")
list
# => [#<Playlist:0x000000028a4f60 @name="Alternative">, #<Playlist:0x000000028e4868 @name="Rock">]
list[0].display
# Your playlist name is Alternative
list[1].display
# Your playlist name is Rock

And now you have a list of playlists! Sweet!

When you get into meta-programming you may use a lot of features from the unconventional method here. meta-programming is where code writes code. It's fun!

Ruby -- use a string as a variable name to define a new variable

There is no way to define new local variables dynamically in Ruby.

It was possible in Ruby 1.8 though with eval 'x = 2'.

You can change an existing variable with eval or binding.local_variable_set.

I would consider using hash to store values.



Related Topics



Leave a reply



Submit