Pickaxe question: "... on the way to true Ruby mastery."

Pickaxe has this code on page 374:

def once(*ids) # :nodoc:
  for id in ids
    module_eval <<-"end;"
    alias_method :__#{id.to_i}__, :#{id.to_s}
    private :__#{id.to_i}__
    def #{id.to_s}(*args, &block)
       (@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0]
    end
end;
end
end

followed by "Understand this code, and you'll be well on the way to
true Ruby mastery." I don't, so I'm not, but would like to, and then I
will (according to the book) - with some help.

I'm good up until the line (@__#{id.to_i}__ ||= [__#{id.to_i}__(*args,
&block)])[0]

Say the id.to_i was 42 ... then this reads: (@__42__ ||= [__42__(*args,
&block)])[ 0 ]

or: (@__42__ ||= [ return value from __42__ method call ] )[ 0 ]

Question: Why can't we just write

(@__42__ ||= return value from __42__ method call)

Why is the [0] necessary at all?

Thanks, (ps - a link to an in-depth explanation would be sufficient!)

Jeff

My guess is it is necessary to keep the value in the array in case the real value is nil. Without it in an array, @__#{id.to_i}__ will evaluate to nil, and the || operator will execute the next term, the function call. You don't want to call the function again, your desired value is already in the instance variable and it is nil.

(@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0]

-andre

···

From: jeffz_2002@yahoo.com
Reply-To: ruby-talk@ruby-lang.org
To: ruby-talk@ruby-lang.org (ruby-talk ML)
Subject: Pickaxe question: "... on the way to true Ruby mastery."
Date: Wed, 3 Jan 2007 13:55:08 +0900

Pickaxe has this code on page 374:

def once(*ids) # :nodoc:
  for id in ids
    module_eval <<-"end;"
    alias_method :__#{id.to_i}__, :#{id.to_s}
    private :__#{id.to_i}__
    def #{id.to_s}(*args, &block)
       (@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0]
    end
end;
end

followed by "Understand this code, and you'll be well on the way to
true Ruby mastery." I don't, so I'm not, but would like to, and then I
will (according to the book) - with some help.

I'm good up until the line (@__#{id.to_i}__ ||= [__#{id.to_i}__(*args,
&block)])[0]

Say the id.to_i was 42 ... then this reads: (@__42__ ||= [__42__(*args,
&block)])[ 0 ]

or: (@__42__ ||= [ return value from __42__ method call ] )[ 0 ]

Question: Why can't we just write

(@__42__ ||= return value from __42__ method call)

Why is the [0] necessary at all?

Thanks, (ps - a link to an in-depth explanation would be sufficient!)

Jeff

_________________________________________________________________
Get live scores and news about your team: Add the Live.com Football Page www.live.com/?addtemplate=football&icid=T001MSN30A0701

Hey Andre, I believe the line reads slightly differently than you've
interpreted ... some more info (note: I still don't have the answer):

= is actually a ruby idiom to initialize a variable if it's nil. In

other words, "a ||= 6" actually means "a = 6 if a.nil?" or "a = 6
unless a". (sorry if I'm lecturing) In other words, if @foo really
*is* a value, and it really *is* nil, I think the function call is
going to be made no matter what ... but again, this is a bit over my
head at present.

So, converting __#{id.to_i}__ to something more readable, like "foo",
this: (@foo ||= [foo(*a, &b)] )[ 0 ]

should be the same as the more verbose:

if @foo
  return @foo[ 0 ] # Still not sure why the [0] is required!
else
  return foo( *a, &b ) # the 0th index of the single array
end

jz

Hi Jeff,

I think this is the intention of the code, that the assignment function is called precisely only once and it is generic, in a sense that it can store any value, including nil and 'false'

Now, you are correct that what you describe there is the behavior of the code 'to be replaced' by 'once'. The intention is the same, namely, the first call returned value should be cached and the subsequence call should just return that cached value, never to assigned again.

The original code, however, has drawback: if the cached value is nil or 'false', the assignment will be made again. The 'once' code is an improvement over the original code that 1) it's more elegant, 2) and it trully assigns only once, even with nil or 'false' cached value.

-andre

···

_________________________________________________________________
Type your favorite song. Get a customized station. Try MSN Radio powered by Pandora. http://radio.msn.com/?icid=T002MSN03A07001

Hi --

Hey Andre, I believe the line reads slightly differently than you've
interpreted ... some more info (note: I still don't have the answer):

>>= is actually a ruby idiom to initialize a variable if it's nil. In
other words, "a ||= 6" actually means "a = 6 if a.nil?" or "a = 6
unless a". (sorry if I'm lecturing) In other words, if @foo really
*is* a value, and it really *is* nil, I think the function call is
going to be made no matter what ... but again, this is a bit over my
head at present.

So, converting __#{id.to_i}__ to something more readable, like "foo",
this: (@foo ||= [foo(*a, &b)] )[ 0 ]

should be the same as the more verbose:

if @foo
return @foo[ 0 ] # Still not sure why the [0] is required!
else
return foo( *a, &b ) # the 0th index of the single array
end

It's more like:

   if @foo
     return @foo[0]
   else
     @foo = [foo(*a, &b)]
     return @foo[0]
   end

or, more simply,

   @foo = [foo(*a, &b)] unless @foo
   return @foo[0]

In other words, @foo gets assigned to, if it's nil to start with.

David

···

On Wed, 3 Jan 2007, jeffz_2002@yahoo.com wrote:

--
Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black\)
    (See what readers are saying! http://www.rubypal.com/r4rrevs.pdf\)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

Thanks David and Andre, I finally get it, and Andre, you're right about
the nil/false idea in your response to my reponse.

For anyone else following this thread, the idea is:

- The first time the method is called, @foo doesn't exist.

- @foo doesn't exist, so it's assigned a single-element array
containing the value returned by the call to the private aliased
original method. This value can be anything, including an array,
false, nil, whatever.

- The 0th array element, which is just the return value of the function
call, is returned.

- The next time the method is called, @foo still exists, and is still a
single-element array. We again return the 0th element, which is the
answer.

The [0] is used because the result is stored in an array ... this gets
around the original method returning nil or false, which would end up
in a second call to the function, had we just naively used @foo ||=
foo( *args, &block )..

Thanks again. It's pretty obvious, actually ... sigh.

Funny, I still don't feel well on my way to mastery. I actually feel
dumber, in a way.

jz

Hi --

Thanks again. It's pretty obvious, actually ... sigh.

Funny, I still don't feel well on my way to mastery. I actually feel
dumber, in a way.

I'd say Dave Thomas's characterization of it as non-obvious and
non-trivial is a good reality check in that regard :slight_smile: And I assure
you that, having studied this example in depth, there's a lot of Ruby
code that you'll take in stride now that you would have found opaque
before. Since you'll take it in stride, you may not *know* that
you're taking it in stride, if you see what I mean -- and that's what
keeps us eager to learn. (And "we" doesn't mean just Ruby programmers
or even just programmers, but people in general who are learning
things.)

David

···

On Thu, 4 Jan 2007, jeffz_2002@yahoo.com wrote:

--
Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black\)
    (See what readers are saying! http://www.rubypal.com/r4rrevs.pdf\)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

jeffz_2002@yahoo.com wrote:

Thanks David and Andre, I finally get it, and Andre, you're right about
the nil/false idea in your response to my reponse.

For anyone else following this thread, the idea is:

- The first time the method is called, @foo doesn't exist.

- @foo doesn't exist, so it's assigned a single-element array
containing the value returned by the call to the private aliased
original method. This value can be anything, including an array,
false, nil, whatever.

- The 0th array element, which is just the return value of the function
call, is returned.

- The next time the method is called, @foo still exists, and is still a
single-element array. We again return the 0th element, which is the
answer.

The [0] is used because the result is stored in an array ... this gets
around the original method returning nil or false, which would end up
in a second call to the function, had we just naively used @foo ||=
foo( *args, &block )..

Thanks again. It's pretty obvious, actually ... sigh.

Funny, I still don't feel well on my way to mastery. I actually feel
dumber, in a way.

jz

Welcome to the wonderful world of meta-programming in Ruby! And don't
feel dumb. As the saying goes: the more you know, the more you know
that you don't know. Ask any true master of anything and that'll be
the answer they give you; if they're a >true< master.

Ken

PS. Check these out to continue your mastery...

http://www.erikveen.dds.nl/monitorfunctions/index.html
http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/22b97167fe614efc/2627d192d2bf56d5?hl=en#2627d192d2bf56d5