Reputation: 578
Total Ruby noob. I'm going through Zed Shaw's LRTHW, and I'm stuck on the Hash exercise. I can't make any sense of this code, and can't find anything on the web that looks like it.
cities = {'CA' => 'San Francisco', 'MI' => 'Detroit', 'FL' => 'Jacksonville'}
cities['NY'] = 'New York'
cities['OR'] = 'Portland'
def find_city (map, state)
if map.include? state
return map[state]
else
return "Not found."
end
end
cities[:find] = method(:find_city)
while true
print "State? (Enter to quit) "
state = gets.chomp
break if state.empty?
puts cities[:find].call(cities, state)
end
Basically, I'm just stuck. I can't make the connection between how I type in a state and it returns the city. Any dumbed down explanation would be greatly appreciated.
Upvotes: 2
Views: 302
Reputation: 5545
Here there are a hash (cities) with 3 pairs key => value
cities = {'CA' => 'San Francisco', 'MI' => 'Detroit', 'FL' => 'Jacksonville'}
You can access that values by the key:
puts cities['CA'] #=> 'San Francisco'
Now we add two new pairs.
cities['NY'] = 'New York'
cities['OR'] = 'Portland'
The whole hash will be:
p cities #=> {'CA' => 'San Francisco', 'MI' => 'Detroit', 'FL' => 'Jacksonville', 'NY' => 'New York', 'OR' => 'Portland'}
Now it's defined a method called find_city
. It takes a hash and a key.
def find_city (map, state)
# if hash has the key, return its value. (There are better ways to do it.)
if map.include? state
return map[state]
else
return "Not found."
end
end
Here there is the worst piece of Ruby code I've ever seen.
cities[:find] = method(:find_city)
Ok, that code gets the method find_city
and turns it into a Method
object, which could be assigned to a variable. BUT, instead of using a normal local variable, it is stored in a value of the cities
hash!
That object is similar to a method, but it's an object instead of a method, and it must be called with call
(in Ruby 1.8.7).
I will use a variable called my_meth
(it could be any name) to explain better.
# get the method find_city and turns it into an object assigned to my_meth
my_meth = method(:find_city)
while true
print "State? (Enter to quit) "
state = gets.chomp
break if state.empty?
# here we use find_city on the cities hash.
puts my_meth.call(cities, state)
end
puts my_meth.class
But, instead to use a variable, the original code stored the Method
object on the cities
hash. So, cities will be:
cities[:find] = method(:find_city)
p cities #=> {'CA' => 'San Francisco', 'MI' => 'Detroit', 'FL' => 'Jacksonville', 'NY' => 'New York', 'OR' => 'Portland', :find => (the Method object)}
So, you can access the find_city
thru cities[:find].call
.
Upvotes: 2
Reputation:
A symbol represents itself, just as 0 represents 0 -- and a symbol is only equal to itself. (Consider that :foo == "foo"
is false, so hash[:foo]
can never refer to the hash["foo"]
pair, even if both may evaluate to the same object).
So cities[:find]
just doesn't make much sense and it confuses the data (hash mapping sates to a big city) with the operation on the data... it might as well be cityFindFunction
instead. However, because using the method itself is fine (no need to convert to a Function here!), consider this simplification:
cities = {'CA' => 'San Francisco', 'MI' => 'Detroit', 'FL' => 'Jacksonville'}
def find_city (map, state)
if map.include? state
# if this hash contains the state, e.g. "CA" then
# return a big city in the state
return map[state]
else
# otherwise return a diagnostic message
return "Not found."
end
end
while true
print "State? (Enter to quit) "
state = gets.chomp
break if state.empty?
# this is a method invocation, no need for "call"!
puts find_city(cities, state)
end
This could be further simplified with returning nil
instead of "Not found." (let the caller deal with it) or using the default-value of a Hash. In any case, I hope that the above shows "the essence" of what is being done.
Happy coding.
A brief bit on a method vs. Method vs Function.
In Ruby, methods are not first class values. Rather, they are "messages" that a given object reacts to. The find_city(cities, state)
method invocation above is convenience for self.__send__(:find_city, cities, state)
. (Well, not really because __send__
is itself a method call, but that's the gist of what happens internally... ;-)
Functions (of type Proc) and objects of type Method, on the other hand, are just objects. As such, unlike methods, they are "first-class values". To invoke/apply a Function/Method object, use the call
method (of the Function/Method) which executes the function with the given parameters and evaluates to the Functions/Methods return value.
The method
method returns a Method object -- bound to the instance of the object method
was invoked upon -- for a given method.
Hope that explains the rest somewhat ;-)
Upvotes: 2
Reputation: 6847
Its not that hard.
Instead of using symbols here you could have used Strings or Integers at least for the hash part. Furthermore you could have used method("find_city") instead of method(:find_city)
You just have to understand basic ruby logic and that you can reference Methods as Objects.
Upvotes: 1