In Progress
Unit 1, Lesson 1
In Progress

Hash To Proc

Video transcript & code

Today's theme is a grocery store setting. We're writing software for a point-of-sale system, and we need an easy way to price food as it comes in off of the conveyor belt.

Accordingly, we have a pricing hash, which maps food types to prices in cents..

PRICING = {
  "bread"  => 2_30,
  "milk"   => 3_82,
  "eggs"   => 2_97,
  "juice"  => 2_98,
  "cheese" => 6_00,
}

Let's say we have a belt full of food, waiting to be scanned and bagged.

To get a price for items, we can use the #map method. We give it a block, which takes each item and looks it up in the pricing map.

require "./pricing"

belt = %w[ juice eggs milk bread eggs cheese juice milk bread ]

belt.map{|item| PRICING[item]}
# => [298, 297, 382, 230, 297, 600, 298, 382, 230]

An interesting fact about associative array data structures, such as Ruby hashes, is that they can also be seen as a function. That is, they are a function which, given a key, returns a value.

In Ruby we can already apply procs and lambdas as if they were hashes. Here's a lambda that behaves just like our pricing hash.

We can "look up" items in this lambda using the square bracket operator, and get prices in return. This works because the square brackets are aliased to the #call method for procs and lambdas.

pricing = ->(item){
  case item
  when "bread" then 2_30
  when "milk" then 3_82
  when "eggs" then 2_97
  when "juice" then 2_98
  when "cheese" then 6_00
  end
}

pricing["bread"]                # => 230
pricing["juice"]                # => 298

…so why can't we turn things around? Why can't we treat hashes as if they were procs?

Well in Ruby 2.3, we finally can. Instead of passing a block, we can just pass the PRICING hash with an ampersand in front of it, the same as we would with any other proc or proc-convertible object. The result is exactly the same as we got with our explicit block.

require "./pricing"

belt = %w[ juice eggs milk bread eggs cheese juice milk bread ]

RUBY_VERSION                    # => "2.3.0"
belt.map(&PRICING)
# => [298, 297, 382, 230, 297, 600, 298, 382, 230]

How is this possible? Well, in Ruby 2.3, Hash gains a #to_proc conversion method.

If we send this message to a hash, we get back a proc. We can call this proc with keys, and get back values.

require "./pricing"

pricer = PRICING.to_proc
# => #<Proc:0x005557fcdd0618>
pricer.call("eggs")             # => 297
pricer.call("cheese")           # => 600

And of course we can do more than just map keys to values. We can use this trick to pass our hashes in lieu of blocks for many enumerable methods. For instance, remember the #sort_by method we learned about in episode #181?

After unique-ing the groceries, we can use our pricing hash as an ampersand-argument to #sort_by to sort them by price!

require "./pricing"
belt = %w[ juice eggs milk bread eggs cheese juice milk bread ]

belt.uniq.sort_by(&PRICING)
# => ["bread", "eggs", "juice", "milk", "cheese"]

So, that's another of the small but helpful changes to look forward to in Ruby 2.3. Happy hacking!

Responses