Forum: Ruby Question about case statement and lambdas

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.
Jeff C. (Guest)
on 2008-10-30 04:25
(Received via mailing list)
I fear this has an obvious answer that I'm just not figuring out.

Sometimes I dive into the Rails source code to see how something works
(don't worry, this isn't a Rails question).  I have been looking at
the implementation of the named_scope feature in Rails, and found some
dense Ruby indeed.  As an exercise, I decided to refactor it to be
more readable and to force me to understand what it's doing.  A series
of simple refactorings has led to a change in behavior, and I'm not
sure why.

Along the way, I ended up with this method (extracted from the
original implementation):

# Starting point
def create_lambda_for_scope(name, options, &block)
  lambda do |parent_scope, *args|
    Scope.new(parent_scope, case options
      when Hash
        options
      when Proc
        options.call(*args)
    end, &block)
  end
end

All tests still pass at this point.. but that inline case statement
bugs me, so I refactor it out to a temporary variable:

# Attempt #2
def create_lambda_for_scope(name, options, &block)
  lambda do |parent_scope, *args|
    arg = case options
      when Proc
        options.call(*args)
      when Hash
        options
    end
    Scope.new(parent_scope, arg, &block)
  end
end

Tests still pass, yessss!  But, I still hate seeing that case
statement.  So I now do this:

# Attempt #3:
   <snip>
    arg = (options.is_a?(Proc) ? options.call(*args) : options)
    Scope.new(parent_scope, arg, &block)
  <snip>

Test still pass, I'm about to declare victory.  But I can't resist one
more simplification to remove the temporary. Since the case statement
is so simple, it looks to me like, if options is a Hash, don't do
anything, but if it's a Proc, then call it.

So I decide to just get rid of the temporary and simply assign a new
value to options only if it's a Proc:

# Attempt #4
  <snip>
    options = options.call(*args) if options.is_a?(Proc)
    Scope.new(parent_scope, options, &block)
 <snip>

Kaboom, several tests now fail.

Is there something I don't understand about the difference between #3
and #4?  I'm guessing it's something to do with the fact that I'm
building a lambda here, so there's closure scope to be concerned
about... but still, #3 and #4 look semantically the same to me.

Any insight or ideas would be appreciated.

Thanks!
Jeff
Peña, Botp (Guest)
on 2008-10-30 04:41
(Received via mailing list)
From: Jeff [mailto:removed_email_address@domain.invalid]
# # Attempt #4
#   <snip>
#     options = options.call(*args) if options.is_a?(Proc)
#     Scope.new(parent_scope, options, &block)
#  <snip>

the only difference i see is that #4 modifies options
David A. Black (Guest)
on 2008-10-30 05:48
(Received via mailing list)
Hi --

On Thu, 30 Oct 2008, Jeff wrote:

> Along the way, I ended up with this method (extracted from the
>    end, &block)
>      when Proc
>
>
>
> Is there something I don't understand about the difference between #3
> and #4?  I'm guessing it's something to do with the fact that I'm
> building a lambda here, so there's closure scope to be concerned
> about... but still, #3 and #4 look semantically the same to me.
>
> Any insight or ideas would be appreciated.

Expanding on Botp's point about options being changed:

The problem is that if options is a Proc the first time you call the
lambda, it isn't the second time. It's still whatever got assigned to
it.

Say options.call(*args) returns "Hello!". The second time you call the
lambda, options will be "Hello!", and not a Proc. "Hello!" will then
be sent to Scope.new, which is probably not going to be right.

That's why you need a temporary variable or an inlined conditional.
You don't want to trample the options variable itself, since it's the
only place where the original options are stored.


David
Brian C. (Guest)
on 2008-10-30 10:23
David A. Black wrote:
> Expanding on Botp's point about options being changed:
>
> The problem is that if options is a Proc the first time you call the
> lambda, it isn't the second time. It's still whatever got assigned to
> it.

And whilst at first glance it seems that 'options' is actually "just" a
local method argument which drops out of scope when the method returns,
it is of course bound into the environment of the lambda (i.e. the
closure), and persists there.
David A. Black (Guest)
on 2008-10-30 14:45
(Received via mailing list)
Hi --

On Thu, 30 Oct 2008, Brian C. wrote:

> closure), and persists there.
Yes, I should have made that explicit -- I had the impression the OP
was hip to that part.


David
Jeff C. (Guest)
on 2008-10-30 15:41
(Received via mailing list)
On Oct 29, 10:47 pm, "David A. Black" <removed_email_address@domain.invalid> 
wrote:

> That's why you need a temporary variable or an inlined conditional.
> Seehttp://www.rubypal.comfor details and updates!
Ah!  Thanks for that explanation!  Makes perfect sense now.

Jeff

purpleworkshops.com
Jeff C. (Guest)
on 2008-10-30 15:42
(Received via mailing list)
On Oct 30, 3:22 am, Brian C. <removed_email_address@domain.invalid> wrote:
> closure), and persists there.
> --
> Posted viahttp://www.ruby-forum.com/.

Excellent... thanks for that insight.

Jeff

purpleworkshops.com
This topic is locked and can not be replied to.