whats this lambda code doing?

Discussion in 'Ruby' started by hemant, Oct 19, 2006.

  1. hemant

    hemant Guest

    I came across following code in typo's application.rb and I can't
    understand the last part,


    def with_blog_scoped_classes(klasses=[Content, Article, Comment,
    Page, Trackback], &block)
    default_id = this_blog.id
    scope_hash = { :find => { :conditions => "blog_id = #{default_id}"},
    :create => { :blog_id => default_id } }
    klasses.inject(block) do |blk, klass|
    lambda { klass.with_scope(scope_hash, &blk) }
    end.call
    end

    Method will be called with a block and will probably add some scope to
    the respective methods of ActiveRecord classes. But whats the last
    part?
    I mean, end.call?
     
    hemant, Oct 19, 2006
    #1
    1. Advertisements


  2. klasses.inject(block) do |blk, klass|
    lambda { klass.with_scope(scope_hash, &blk) }
    end.call

    That outer block that ends on end returns the lambda made inside of
    that block. So end.call ends up being lambda { klass.with_scope
    (scope_hash, &blk) }.call




    -- Ezra Zygmuntowicz
    -- Lead Rails Evangelist
    --
    -- Engine Yard, Serious Rails Hosting
    -- (866) 518-YARD (9273)
     
    Ezra Zygmuntowicz, Oct 19, 2006
    #2
    1. Advertisements

  3. --------------enig783383C0715435A317AC8FB3
    Content-Type: text/plain; charset=ISO-8859-1
    Content-Transfer-Encoding: quoted-printable
    The call to klasses.inject returns a block (a Proc). The .call evaluates
    the Proc.

    The code is a bit too clever for its own good. Personally I could go
    around and slap everyone that chains a method after a code block /
    closure block, or tags a statement modifier conditional / loop after it,
    most of the time it's just golfing to hide an unsightly level of control
    flow nesting.

    David Vallner


    --------------enig783383C0715435A317AC8FB3
    Content-Type: application/pgp-signature; name="signature.asc"
    Content-Description: OpenPGP digital signature
    Content-Disposition: attachment; filename="signature.asc"

    -----BEGIN PGP SIGNATURE-----
    Version: GnuPG v1.4.5 (MingW32)

    iD8DBQFFNs0wy6MhrS8astoRAoiFAJ4wrd3lgcPAxMzWaMhN2PDmFOIQKACffzTK
    xx68vIKlZFUFivXxaqFeK4c=
    =MIOp
    -----END PGP SIGNATURE-----

    --------------enig783383C0715435A317AC8FB3--
     
    David Vallner, Oct 19, 2006
    #3
  4. hemant

    Ken Bloom Guest

    The whole do...end thing (and its binding to the inject call) has higher
    precedence than the method call that follows it. It's equivalent to

    x=klasses.inject(block) do
    ...
    end
    x.call
     
    Ken Bloom, Oct 19, 2006
    #4
  5. --------------enigAB7DF8666B9EAF2F862CA995
    Content-Type: text/plain; charset=ISO-8859-1
    Content-Transfer-Encoding: quoted-printable

    Hmm. Unless I'm very mistaken, it would also only call the lambda for
    the last object in klasses, generating several garbage (and relatively
    expensive) lambdas. Boggle. Am I missing something?

    David Vallner


    --------------enigAB7DF8666B9EAF2F862CA995
    Content-Type: application/pgp-signature; name="signature.asc"
    Content-Description: OpenPGP digital signature
    Content-Disposition: attachment; filename="signature.asc"

    -----BEGIN PGP SIGNATURE-----
    Version: GnuPG v1.4.5 (MingW32)

    iD8DBQFFNs6Sy6MhrS8astoRApXlAJ0SdVtCZKSa01ConRk/rDekzmktLwCeOFnJ
    DwIa0kDSm7HK8l5lxI64jNo=
    =ik4D
    -----END PGP SIGNATURE-----

    --------------enigAB7DF8666B9EAF2F862CA995--
     
    David Vallner, Oct 19, 2006
    #5

  6. It looks like it would only call the last lambda but it does call
    all of them. Here is a simplification of whats happening.

    irb(main):065:0> def scoper(klasses=[:foo, :bar, :baz], &block)
    irb(main):066:1> klasses.inject(block) do |blk, klass|
    irb(main):067:2* lambda { puts klass; blk.call}
    irb(main):068:2> end.call
    irb(main):069:1> end
    => nil
    irb(main):070:0* scoper { puts '&block called' }
    baz
    bar
    foo
    &block called
    => nil
    irb(main):071:0>



    -- Ezra Zygmuntowicz
    -- Lead Rails Evangelist
    --
    -- Engine Yard, Serious Rails Hosting
    -- (866) 518-YARD (9273)
     
    Ezra Zygmuntowicz, Oct 19, 2006
    #6

  7. with_scope is an ActiveRecord thing for scoping your where clauses.
    It works like this:

    Post.with_scope:)find => {:conditions => ["customer_id = ?",
    @customer.id]}) do
    @post = Post.find:)all)
    end

    And you can nest these with_scope blocks inside each other so the
    innermost #find method gets the scoped :conditions merged with its
    own conditions. So the original posters code is taking a list of
    Model classes and scoping the find and create calls on them to a
    specific blog.id .

    But I have to agree that code is too clever for its own good.
    Twisting it up like that may make a golfer happy, the next guy to
    come across it will be confused when it could have been stated clearer.

    Cheers.



    -- Ezra Zygmuntowicz
    -- Lead Rails Evangelist
    --
    -- Engine Yard, Serious Rails Hosting
    -- (866) 518-YARD (9273)
     
    Ezra Zygmuntowicz, Oct 19, 2006
    #7
  8. I think I'm still confused by this. I infer from your post that the
    lambdas get nested by the inject (at the position of 'blk') with the
    first one innermost, which is why they get called in reverse order
    (and 'block' last of all). Is that right?

    Regards, Morton
     
    Morton Goldberg, Oct 19, 2006
    #8
  9. Yes thats essentially it. Maybe this helps clear it up a bit. Lets
    forget about the with_scope AR stuff for a minute and just mock this
    out:

    class Scoper
    def self.with_block(hsh={}, &block)
    p hsh.merge( {:klass => name})
    block.call
    end
    end

    class Foo < Scoper
    end

    class Bar < Scoper
    end

    class Baz < Scoper
    end

    def set_scopers(klasses=[Foo, Bar, Baz], &block)
    hash = {:test => 'test'}
    klasses.inject(block) do |blk, klass|
    lambda { klass.with_block hash, &blk }
    end.call
    end

    puts set_scopers { puts 'done'}

    # outputs
    {:test=>"test", :klass=>"Baz"}
    {:test=>"test", :klass=>"Bar"}
    {:test=>"test", :klass=>"Foo"}
    done



    Now if you comment out the block.call line in the Scoper.with_block
    method:

    class Scoper
    def self.with_block(hsh={}, &block)
    p hsh.merge( {:klass => name})
    #block.call
    end
    end

    <snip>

    puts set_scopers { puts 'done' }
    #ouput
    {:test=>"test", :klass=>"Baz"}
    nil


    and run the same code again it only calls the last lambda that gets
    made. It is a twisty little mess of block and wrappers that is hard
    to follow. But essentially inject is chaning those lambda's together
    and the final .call calls the chain which calls the final &blk in the
    end.
    nil

    Cheers-

    -- Ezra Zygmuntowicz
    -- Lead Rails Evangelist
    --
    -- Engine Yard, Serious Rails Hosting
    -- (866) 518-YARD (9273)
     
    Ezra Zygmuntowicz, Oct 19, 2006
    #9
  10. You know, I can understand, or maybe sympathize, with the appeal of
    real golf, although I've yet to break 110, but I can't for the life of
    me understand why anyone things code golfing is good for anyone.

    But then again "Succintness is Power!"
     
    Rick DeNatale, Oct 19, 2006
    #10
  11. hemant

    hemant Guest

    Hmm...i think i get the idea, but i do not understand how its .call is
    working on all the lambdas?
     
    hemant, Oct 19, 2006
    #11
  12. hemant

    hemant Guest

    Ok..i get it now..Thanks for the nice explanation Ezra.
     
    hemant, Oct 19, 2006
    #12

  13. What it is doing with the inject is essentially building up a block
    with multiple procs inside it and then calling that. So the block
    that gets called looks something like this by the time it gets call'ed:

    lambda {
    lambda { Foo.with_block hash, &blk }
    lambda { Bar.with_block hash, &blk }
    lambda { Baz.with_block hash, &blk }
    }.call


    Make sense?

    -- Ezra Zygmuntowicz
    -- Lead Rails Evangelist
    --
    -- Engine Yard, Serious Rails Hosting
    -- (866) 518-YARD (9273)
     
    Ezra Zygmuntowicz, Oct 19, 2006
    #13
  14. Maybe I'm being even more dense than usual, but I can't accept that.
    It wouldn't produce the observed print-out order. I think it must be
    more like:

    lambda { Baz.with_block(hash,
    lambda { Bar.with_block(hash,
    lambda { Foo.with_block(hash, &blk) }) }) }.call

    Regards, Morton
     
    Morton Goldberg, Oct 19, 2006
    #14
  15. Ahh yes you are totally correct. See? It is tricky code ;)


    -- Ezra Zygmuntowicz
    -- Lead Rails Evangelist
    --
    -- Engine Yard, Serious Rails Hosting
    -- (866) 518-YARD (9273)
     
    Ezra Zygmuntowicz, Oct 19, 2006
    #15
  16. It's really not that bad. Learn to love the lambda, I say =)

    unwind = lambda {|x| puts x}
    (1..9).inject(unwind) do |stack, i|
    lambda {|x| puts x; stack}
    end[10]
     
    Louis J Scoras, Oct 19, 2006
    #16
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.