she knows what we do

Posted by curt on January 21st, 2007

My two year old knows what my wife and I do in the night.

NO, not that…

She knows we eat ice cream after she goes to bed! The other morning she saw a bowl left sitting out from the night before with a tiny hint of mint chocolate chip. Her exact words were, "Mom and Dad eat green ice cream in the night." She's a smart one.

Ruby: Recursive send

Posted by curt on January 18th, 2007

Ruby's Object#send allows dynamic calling of a method. This is very useful, but what if we wanted to call several levels deep on an object? For instance:

# Normal call chain
post.comments.first.commented_at

# Dynamically with send?  Have to call three times.
post.send(:comments).send(:first).send(:commented_at)

What if the number of calls to send is variable depending on what we're trying to show? In one case we might need post.posted_at for the date, and in another case we might need post.comments.first.commented_at for the date.

How could we dynamically craft the definition of the methods to send if we don't know how many calls to Object#send we'll have? We need a way to define an arbitrary number of method calls.

Behold, a recursive send: Object#rsend

class Object
  def rsend(*args, &block)
    obj = self
    args.each do |a|
      b = (a.is_a?(Array) && a.last.is_a?(Proc) ? a.pop : block)
      obj = obj.__send__(*a, &b)
    end
    obj
  end
  alias_method :__rsend__, :rsend
end

Each argument passed to Object#rsend is an array with the symbols and arguments that will be passed on to Object#send:

post.rsend([:comments],[:first],[:commented_at])

If there are no arguments to be passed on to send, the array brackets can be omitted:

post.rsend(:comments, :first, :commented_at)

Of course, in practice you'll probably be defining your method call chain in one part of your code, putting it in a variable, and sending it to rsend with a splat*:

the_date = [:comments, :first, :commented_at]

#…somewhere else in your code you've passed the_date along:
post.rsend(*the_date)

With arguments:

a = [0,1,2,3,4,5,6,7,8,9]

a.rsend([:slice, 2, 8]) #=> [2, 3, 4, 5, 6, 7, 8, 9]

a.rsend([:slice, 2, 8], [:slice, 1, 3]) #=> [3, 4, 5]

Object#send accepts a block. What about blocks? Pass in a proc:

a.rsend([:map, (proc { |x| x*2 })])
  #=> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
 
a.rsend([:map, (proc { |x| x*2 })],
        [:select, (proc { |x| x % 4 == 0})])
  #=> [0, 4, 8, 12, 16]

And, in an effort to make Object#rsend behave like Object#send for the simple case, you can send a regular block:

a.rsend(:map) { |x| x*2 }
  #=> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Caveat: For the case needing parameters, Object#rsend does require an array, so:

a.rsend(:slice, 2, 8) # wrong, does not work like Object#send

a.rsend([:slice, 2, 8]) # right

A quirk that I've left in for fun, but it might (and maybe should) change: If providing a single block, that block will be called on every call unless you've already passed in a proc:

a.rsend(:map, :map) { |x| x*2 }
  #=> [0, 4, 8, 12, 16, 20, 24, 28, 32, 36]
 
a.rsend(:map, [:map, (proc { |x| x+5 })], :map) { |x| x*2 }
  #=> [10, 14, 18, 22, 26, 30, 34, 38, 42, 46]
  #outer block was called on first and third :map

Can anyone come up with a good use for this call-the-block-each-time behavior?

Has anyone done this already? I searched for such a thing and came up empty. Maybe this method should be called something else? I named it based on each call recursing down the chain of methods with a new object being returned for the next method to be sent to.

Suggestions and comments are welcome.

If you give a frog a burrito

Posted by curt on January 9th, 2007

Those of you with small children may be familiar with the "If You Give a…" books by Laura Joffe Numeroff (e.g., "If You Give a Mouse a Cookie" and "If You Give a Pig a Party"). Here's a silly story with the same feel:

If you give a frog a burrito,
he's probably going to ask you for some hot sauce.
When he sees the joke on the hot sauce packet,
he'll want to see them all.
You'll have to dig through the condiment bucket to
find as many sayings as you can.

When he's finished with the burrito,
he's going to ask you for some Rolaids.
You'll go together to the drugstore to buy them.
While you're at the drugstore,
he'll want to take his blood pressure
on one of those sit down, arm clamp machines.
He'll take his blood pressure and eat his Rolaids.

On your way out of the drugstore, he'll remember that
his pictures are waiting at the photo counter.
You go back inside to get the photos.
He'll want you to see them right away,
so you'll sit in the car and flip through the photos.
It's cold outside, so he'll want you to keep the car running
with the heat on while you browse all 432 photos.
He got double prints, so he'll want you to help him separate
the pictures into two nice stacks.

One of the photos is a picture of the frog's house.
Now that you've seen his house, he'll want to see yours.
You'll drive home.
When you get home, you'll empty your pockets on the counter.
The frog will see one of the funny hot sauce packets
that stowed away in your pocket.
He'll ask for the hot sauce packet.
And, chances are, if you give him the hot sauce,
he's going to ask for a burrito.


Copyright © 2007 csummers.org. All rights reserved.