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

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
unknown (Guest)
on 2007-01-03 06:57
(Received via mailing list)
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
Andreas S (Guest)
on 2007-01-03 07:29
(Received via mailing list)
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
unknown (Guest)
on 2007-01-03 08:01
(Received via mailing list)
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
Andreas S (Guest)
on 2007-01-03 09:29
(Received via mailing list)
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
unknown (Guest)
on 2007-01-03 11:21
(Received via mailing list)
Hi --

On Wed, 3 Jan 2007, removed_email_address@domain.invalid wrote:

> 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
unknown (Guest)
on 2007-01-03 20:05
(Received via mailing list)
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
unknown (Guest)
on 2007-01-03 21:12
(Received via mailing list)
Hi --

On Thu, 4 Jan 2007, removed_email_address@domain.invalid wrote:

> 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 T.'s characterization of it as non-obvious and
non-trivial is a good reality check in that regard :-)  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
Kenosis (Guest)
on 2007-01-04 00:56
(Received via mailing list)
removed_email_address@domain.invalid wrote:
> false, nil, whatever.
> 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/brow...
This topic is locked and can not be replied to.