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.
8217faf2bfdfa7daf10135d41ddd421e?d=identicon&s=25 Jeff Cohen (jeff)
on 2008-10-30 03: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
6087a044557d6b59ab52e7dd20f94da8?d=identicon&s=25 Peña, Botp (Guest)
on 2008-10-30 03:41
(Received via mailing list)
From: Jeff [mailto:cohen.jeff@gmail.com]
# # 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
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2008-10-30 04: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
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2008-10-30 09: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.
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2008-10-30 13:45
(Received via mailing list)
Hi --

On Thu, 30 Oct 2008, Brian Candler wrote:

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


David
8217faf2bfdfa7daf10135d41ddd421e?d=identicon&s=25 Jeff Cohen (jeff)
on 2008-10-30 14:41
(Received via mailing list)
On Oct 29, 10:47 pm, "David A. Black" <dbl...@rubypal.com> 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
8217faf2bfdfa7daf10135d41ddd421e?d=identicon&s=25 Jeff Cohen (jeff)
on 2008-10-30 14:42
(Received via mailing list)
On Oct 30, 3:22 am, Brian Candler <b.cand...@pobox.com> 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.