Use a string as a method call

C

Chris Bailey

I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
do_foo()
elsif input == bar
do_bar()
else
puts "That isn't a command!"
end

What I would like to do is more like so.

commands = {
'foo' => do_foo(),
'bar' => do_bar()
}

I then would like to search the commands hash for a key that matches the
player input and execute the method associated with that key. What I've
noticed is that upon initialization of the hash the value becomes equal
to the result of the method but that is not what I want. If I store the
value as a string ie "do_foo()" would I be able to parse that string and
execute it as a method? And if so, how would that be done?
 
G

Glen Holcomb

[Note: parts of this message were removed to make it a legal post.]

On Thu, Aug 28, 2008 at 2:02 PM, Chris Bailey <
I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
do_foo()
elsif input == bar
do_bar()
else
puts "That isn't a command!"
end

What I would like to do is more like so.

commands = {
'foo' => do_foo(),
'bar' => do_bar()
}

I then would like to search the commands hash for a key that matches the
player input and execute the method associated with that key. What I've
noticed is that upon initialization of the hash the value becomes equal
to the result of the method but that is not what I want. If I store the
value as a string ie "do_foo()" would I be able to parse that string and
execute it as a method? And if so, how would that be done?
You could do it with eval in this case it sounds like that would be fairly
safe although you could also use a proc I think.
 
T

Thomas B.

Chris said:
I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
do_foo()
elsif input == bar
do_bar()
else
puts "That isn't a command!"
end

ALLOWED=[:foo,:bar]
input=gets.downcase.chomp.to_sym
if ALLOWED.include? input
send(input)
else
puts "That isn't a command!"
end

The method send calls a method specified as the first argument to send.
Try send:)puts,"abc") as an example.

TPR.
 
G

Gregory Brown

I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
do_foo()
elsif input == bar
do_bar()
else
puts "That isn't a command!"
end

What I would like to do is more like so.

commands = {
'foo' => do_foo(),
'bar' => do_bar()
}


ACTIONS = %w[foo bar]

def execute(action)
return send("do_#{action}") if ACTIONS.include?(action)
raise "Unexpected action"
end
 
J

John Pritchard-williams

Hi Chris,

Try this:

...
class MyClass
def mymethod
puts "mymethod called!"
end

def myothermethod
puts "myothermethod called!"
end
end


input = gets.downcase.chomp;
myinstance=MyClass.new;
myinstance.send(input.to_sym);
...

So use the 'send' method of your instance to invoke the method - first
convert the inputted string to a symbol. Wow..you gotta love Ruby for
stuff like this....:)

Cheers

John
 
S

Stefano Crocco

I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
do_foo()
elsif input == bar
do_bar()
else
puts "That isn't a command!"
end

What I would like to do is more like so.

commands = {
'foo' => do_foo(),
'bar' => do_bar()
}

I then would like to search the commands hash for a key that matches the
player input and execute the method associated with that key. What I've
noticed is that upon initialization of the hash the value becomes equal
to the result of the method but that is not what I want. If I store the
value as a string ie "do_foo()" would I be able to parse that string and
execute it as a method? And if so, how would that be done?

You can do something like this:

method_name = gets.downcase.chomp
send method_name

send is a method which takes as a first argument a method name (as a string or
symbol) and calls that method on its receiver, passing all the remaining
arguments to it (see ri Object#send for a better explaination). To better
handle the possibility the user inserts a wrong string, you can rescue the
NoMethodError exception:

method_name = gets.downcase.chomp
begin
send method_name
rescue NoMethodError
puts "#{method_name} is not a valid command"
end

If you want to restrict the methods the user can call, store them in an array
and check whether the string he entered is there before calling send:

commands = %w[foo bar]
method_name = gets.downcase.chomp
if commands.include? method_name then send method_name
else puts "#{method_name} is not a valid command"
end

I hope this helps

Stefano
 
S

Sebastian Hungerecker

John said:
So use the 'send' method of your instance to invoke the method - first
convert the inputted string to a symbol.

Actually you don't need to do that. send takes strings too.

HTH,
Sebastian
 
S

Stefano Crocco

I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
do_foo()
elsif input == bar
do_bar()
else
puts "That isn't a command!"
end

What I would like to do is more like so.

commands = {
'foo' => do_foo(),
'bar' => do_bar()
}

I then would like to search the commands hash for a key that matches the
player input and execute the method associated with that key. What I've
noticed is that upon initialization of the hash the value becomes equal
to the result of the method but that is not what I want. If I store the
value as a string ie "do_foo()" would I be able to parse that string and
execute it as a method? And if so, how would that be done?

You can do something like this:

method_name = gets.downcase.chomp
send method_name

send is a method which takes as a first argument a method name (as a string
or symbol) and calls that method on its receiver, passing all the remaining
arguments to it (see ri Object#send for a better explaination). To better
handle the possibility the user inserts a wrong string, you can rescue the
NoMethodError exception:

method_name = gets.downcase.chomp
begin
send method_name
rescue NoMethodError
puts "#{method_name} is not a valid command"
end

If you want to restrict the methods the user can call, store them in an
array and check whether the string he entered is there before calling send:

commands = %w[foo bar]
method_name = gets.downcase.chomp
if commands.include? method_name then send method_name
else puts "#{method_name} is not a valid command"
end

I hope this helps

Stefano

Of course, with this approach, you'll need to change the names of your methods
to foo and bar, or to change the definition of method_name to something like
this:

method_name = "do_#{gets.downcase.chomp}"

Stefano
 
C

Chris Bailey

Thank you everyone for the quick and helpful replies! I toyed around
with all of the suggestions and decided the following would be the best
choice in my application.

def parse
input = gets.downcase.chomp
if $cmd_list.key?(input)
send $cmd_list[input]
else
puts "#{input} is not a valid command."
end
end

I have however ran into another problem and my limited knowledge of send
(I hadn't heard of it until I read these replies) makes it difficult for
me to debug. Some of my methods that worked perfectly before accessing
them using send are no longer working properly. Consider the following.

cmd_list = {
'north' => 'do_north'
'south' => 'do_south'
'n' => 'do_north'
's' => 'do_south'
'look' => 'do_look'
}

def cmd_north
$PLROBJ.xcoord -=
showMap()
end

def cmd_south
$PLROBJ.xcoord +=
showMap()
end

def cmd_look
showMap
end

If the user input is "look", the showMap method is called properly, and
everything works as intended. However if north,south,n or s is typed
into the system it fails to update the $PLROBJ variable but executes
showMap() as usual and then crashes with the following error.

/commands.rb:16:in `-': nil can't be coerced into Fixnum (TypeError)
from ./commands.rb:16:in `cmd_north'
from bsud.rb:33:in `send'
from bsud.rb:33:in `parse'
from bsud.rb:58:in `main'
from bsud.rb:63


$PLROBJ.xcoord and ycoord are initiated with a value of 5 and as far as
I know there is nothing that would ever make them 'nil'. I have no idea
what this problem is =(
 
T

Thomas B.

Chris said:
send $cmd_list[input]
cmd_list = {
'north' => 'do_north'
'south' => 'do_south'
'n' => 'do_north'
's' => 'do_south'
'look' => 'do_look'
}
The variable names don't agree but I suppose it's a typo in the post?

Well, what are you trying to do? If you want to increment or decrement
$PLROBJ.xcoord, you must say $PLROBJ.xcoord+=1 or $PLROBJ.xcoord-=1.
What happens now, the variable $PLROBJ.xcoord is incremented or
decremented by the result of showMap, like in $PLROBJ.xcoord-=showMap(),
and showMap probably returns nil.

TPR
 
C

Chris Bailey

Thomas said:
Chris said:
send $cmd_list[input]
cmd_list = {
'north' => 'do_north'
'south' => 'do_south'
'n' => 'do_north'
's' => 'do_south'
'look' => 'do_look'
}
The variable names don't agree but I suppose it's a typo in the post?

Well, what are you trying to do? If you want to increment or decrement
$PLROBJ.xcoord, you must say $PLROBJ.xcoord+=1 or $PLROBJ.xcoord-=1.
What happens now, the variable $PLROBJ.xcoord is incremented or
decremented by the result of showMap, like in $PLROBJ.xcoord-=showMap(),
and showMap probably returns nil.

TPR

Arg! The variable mismatch was a typo, and the error is because I was
using += instead of +=1. Dumb mistake on my part. Yesterday I was trying
to use ++
Thanks
 
D

David Masover

ALLOWED=[:foo,:bar]
input=gets.downcase.chomp.to_sym
if ALLOWED.include? input
send(input)
else
puts "That isn't a command!"
end

Subtle memory leak here: A symbol, once defined, is never collected. (Unless
this has changed, but I don't see how it could...) They have many advantages,
and I'd still use them in code, but don't use them with user input. Instead:

ALLOWED = %w{foo bar}.map(&:freeze).freeze
input = gets.downcase.chomp
if ALLOWED.include? input
send input
else
puts "'#{input}' isn't a command!"
end

A possible performance hack is to use a Set instead, but I'm not sure how
large the array has to be for this to actually be faster:

require 'set'
ALLOWED = Set.new(%w{foo bar}).freeze


And yes, "send" seems to work with strings. No point in casting to a symbol if
you're only going to be using it for one send call.


Of course, I wouldn't worry about it if you're only accepting input from one
user, at the commandline -- they'd have to bang on the keyboard quite awhile
to use any significant amount of RAM.

But it's something to consider when building any kind of long-running service.
 
A

Andraes Tsepesh

Chris said:
I then would like to search the commands hash for a key that matches the
player input and execute the method associated with that key. What I've
noticed is that upon initialization of the hash the value becomes equal
to the result of the method but that is not what I want. If I store the
value as a string ie "do_foo()" would I be able to parse that string and
execute it as a method? And if so, how would that be done?

First time poster, and I was so surprised I was able to find this
online. I've been working on figuring this out for hours. I've adopted
the "send(value)" solution and it works great.

My question is, how can I pass arguments to methods called this way? My
hash is pretty much identical, here's a sample:

commands = {"look" => "doLook"}

I have split the input variable so I can accept more than 1 word at a
time (input can be anything from ["look"] to ["look", "at", "the",
"desk"]). I'd like to be able to pass the input variable to these
methods, but am having difficulty with that. I have tried modifying my
hash as so:

commands = {"look" => "doLook(input)"}

with no luck. I've also modified my send as so:

send(commands[x](input))

with no luck. Any ideas? Thanks a bunch for any assistance anyone can
provide :)
 
A

Andraes Tsepesh

Andraes said:
My question is, how can I pass arguments to methods called this way? My
hash is pretty much identical, here's a sample:

Hah, I figured it out. This always happens - I have to formally type out
my request in a help forum before I can figure it out lol
 

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,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top