lambda and multiple arguments: how?!

Discussion in 'Ruby' started by nvp, Oct 28, 2008.

  1. nvp

    nvp Guest

    Hello,

    I've been working on porting a useful Perl module to Ruby and have
    been struggling with the differences between Perl's function
    references and Ruby's lambda, proc, etc.

    Basically, my Perl module has two functions that do this:

    sub run_on_finish { my ($s,$code,$pid)=@_;
    $s->{on_finish}->{$pid || 0}=$code;
    }

    sub on_finish { my ($s,$pid,@par)=@_;
    my $code=$s->{on_finish}->{$pid} || $s->{on_finish}->{0} or return 0;
    $code->($pid,@par);
    };

    Using the above, I can do the following:

    $obj->run_on_finish(sub { my($pid, $exit_code, $ident) = @_;
    # Do something with $pid, $exit_code, $ident
    });

    In Ruby, things are more difficult given that I don't fully understand
    closures yet. I've reimplemented the methods like so:

    # module
    def run_on_finish(code, pid=0)
    begin
    self.do_on_finish[pid] = code
    rescue
    raise "error in run_on_finish\n" # Message stinks
    end
    end

    def on_finish(pid, *params)
    code = self.do_on_finish[pid] || self.do_on_finish[0] or return 0
    begin
    code.call(pid, params)
    rescue
    raise "lameness\n" # Message stinks
    end
    end

    # client
    pfm.run_on_finish(
    lambda {
    |pid,exit_code,ident|
    print "LEN ", pid.length(), " -- ", "MY ARGS ", pid.join(':'), "\n"
    }
    )

    As you can see, I'm calling self.do_on_finish with the arguments
    'pid', and an array of 'params'.
    Unfortunately Ruby only matches |pid,<second_arg>|, and this is not
    the behavior I'd expect or
    want.

    Outside of setting |*params| in lambda {} such that I would check/set
    my values manually, is
    there a way to provide lambda with more than two arguments?

    --
    nvp
    "Of course, the US has many elements of socialism already since
    capitalists, like cats, tend to get themselves stuck up a tree every
    so often." -- Joe Johnston
     
    nvp, Oct 28, 2008
    #1
    1. Advertising

  2. Hi --

    On Wed, 29 Oct 2008, nvp wrote:

    > Hello,
    >
    > I've been working on porting a useful Perl module to Ruby and have
    > been struggling with the differences between Perl's function
    > references and Ruby's lambda, proc, etc.
    >
    > Basically, my Perl module has two functions that do this:
    >
    > sub run_on_finish { my ($s,$code,$pid)=@_;
    > $s->{on_finish}->{$pid || 0}=$code;
    > }
    >
    > sub on_finish { my ($s,$pid,@par)=@_;
    > my $code=$s->{on_finish}->{$pid} || $s->{on_finish}->{0} or return 0;
    > $code->($pid,@par);
    > };
    >
    > Using the above, I can do the following:
    >
    > $obj->run_on_finish(sub { my($pid, $exit_code, $ident) = @_;
    > # Do something with $pid, $exit_code, $ident
    > });
    >
    > In Ruby, things are more difficult given that I don't fully understand
    > closures yet. I've reimplemented the methods like so:
    >
    > # module
    > def run_on_finish(code, pid=0)
    > begin
    > self.do_on_finish[pid] = code
    > rescue
    > raise "error in run_on_finish\n" # Message stinks
    > end
    > end
    >
    > def on_finish(pid, *params)
    > code = self.do_on_finish[pid] || self.do_on_finish[0] or return 0
    > begin
    > code.call(pid, params)
    > rescue
    > raise "lameness\n" # Message stinks
    > end
    > end
    >
    > # client
    > pfm.run_on_finish(
    > lambda {
    > |pid,exit_code,ident|
    > print "LEN ", pid.length(), " -- ", "MY ARGS ", pid.join(':'), "\n"
    > }
    > )
    >
    > As you can see, I'm calling self.do_on_finish with the arguments
    > 'pid', and an array of 'params'.
    > Unfortunately Ruby only matches |pid,<second_arg>|, and this is not
    > the behavior I'd expect or
    > want.
    >
    > Outside of setting |*params| in lambda {} such that I would check/set
    > my values manually, is
    > there a way to provide lambda with more than two arguments?


    You could call it as: code.call(pid,*params), or maybe try this
    technique:

    func = lambda {|a,(b,c)| p a,b,c }
    func.call(1,[2,3])

    The (b,c) notation will cause the array to be spread over those two
    parameters.


    David

    --
    Rails training from David A. Black and Ruby Power and Light:
    Intro to Ruby on Rails January 12-15 Fort Lauderdale, FL
    Advancing with Rails January 19-22 Fort Lauderdale, FL *
    * Co-taught with Patrick Ewing!
    See http://www.rubypal.com for details and updates!
     
    David A. Black, Oct 28, 2008
    #2
    1. Advertising

  3. nvp

    nvp Guest

    On Tue, Oct 28, 2008 at 1:36 PM, David A. Black <> wrote:
    > You could call it as: code.call(pid,*params),


    This didn't work for me with:

    lambda {
    |pid,exit_code,ident|
    # ...
    }

    What am I missing here?

    > or maybe try this technique:
    >
    > func = lambda {|a,(b,c)| p a,b,c }
    > func.call(1,[2,3])


    Very interesting and seems to work as I was hoping!

    Thanks!

    --
    nvp
    "Of course, the US has many elements of socialism already since
    capitalists, like cats, tend to get themselves stuck up a tree every
    so often." -- Joe Johnston
     
    nvp, Oct 28, 2008
    #3
  4. 2008/10/28 nvp <>:
    > On Tue, Oct 28, 2008 at 1:36 PM, David A. Black <> wrote:
    >> You could call it as: code.call(pid,*params),

    >
    > This didn't work for me with:
    >
    > lambda {
    > |pid,exit_code,ident|
    > # ...
    > }
    >
    > What am I missing here?


    You need to adjust the parameter list to the arity of the block.
    Here's a way how to do it

    11:38:30 Temp$ cat lambda.rb
    @code = lambda {|a,b,c| printf "R: %p %p %p\n",a,b,c}
    def run1(*params)
    ar = @code.arity
    if ar >= 0
    params = params[]
    params.concat(Array.new(ar - params.size))
    end
    @code[*params]
    @code.call(*params)
    end
    def run2(*params)
    params.unshift :pid
    ar = @code.arity
    if ar >= 0
    params = params[]
    params.concat(Array.new(ar - params.size))
    end
    @code[*params]
    @code.call(*params)
    end
    run1 1
    run1 1,2
    run1 1,2,3
    run2 1
    run2 1,2
    run2 1,2,3
    11:38:32 Temp$ ruby lambda.rb
    R: 1 nil nil
    R: 1 nil nil
    R: 1 2 nil
    R: 1 2 nil
    R: 1 2 3
    R: 1 2 3
    R: :pid 1 nil
    R: :pid 1 nil
    R: :pid 1 2
    R: :pid 1 2
    R: :pid 1 2
    R: :pid 1 2
    11:38:33 Temp$

    Btw, IMHO you have a slight design inconsistency in your code: the
    client registers a lambda with a certain pid so it *knows* the pid
    already. But still you provide it to the block when calling it. That
    seems superfluous because even if the pid is stored in a local
    variable the block can access it because it is a closure:

    irb(main):001:0> def bl
    irb(main):002:1> pid = Time.now.to_i # random
    irb(main):003:1> lambda { puts "pid is #{pid}" }
    irb(main):004:1> end
    => nil
    irb(main):005:0> x = bl
    => #<Proc:0x7ff8e04c@(irb):3>
    irb(main):006:0> x[]
    pid is 1225276874
    => nil
    irb(main):007:0> x[]
    pid is 1225276874
    => nil
    irb(main):008:0> x[]
    pid is 1225276874
    => nil
    irb(main):009:0> x[]
    pid is 1225276874
    => nil
    irb(main):010:0>

    Kind regards

    robert

    --
    remember.guy do |as, often| as.you_can - without end
     
    Robert Klemme, Oct 29, 2008
    #4
  5. nvp

    nvp Guest

    On Wed, Oct 29, 2008 at 6:41 AM, Robert Klemme
    <> wrote:
    >
    > You need to adjust the parameter list to the arity of the block.
    > Here's a way how to do it
    >
    > 11:38:30 Temp$ cat lambda.rb
    > @code = lambda {|a,b,c| printf "R: %p %p %p\n",a,b,c}
    > def run1(*params)

    [snip]

    Ah, I get it now! Took awhile to digest but this was perfect (checking
    arity and reassigning params based on value of arity - array.size was
    the key to the whole thing). Thanks!

    > Btw, IMHO you have a slight design inconsistency in your code: the
    > client registers a lambda with a certain pid so it *knows* the pid
    > already. But still you provide it to the block when calling it. That
    > seems superfluous because even if the pid is stored in a local
    > variable the block can access it because it is a closure:


    I fixed my code as a result so pid is no longer an argument in said
    functions. As it turns out, having pid as an argument was ambiguous
    as far as Ruby/lambda was concerned and it made reassigning
    params far too difficult. Since I was already assigning item[pid] = code,
    I merely checked for item[params[0]] before doing the obj.call(*params)
    and raising an error if item[params[0]] didn't exist.

    --
    nvp
    "Of course, the US has many elements of socialism already since
    capitalists, like cats, tend to get themselves stuck up a tree every
    so often." -- Joe Johnston
     
    nvp, Nov 1, 2008
    #5
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Roman Suzi
    Replies:
    13
    Views:
    608
    Bengt Richter
    Jan 7, 2005
  2. nico

    lambda a plusieurs arguments

    nico, May 27, 2005, in forum: Python
    Replies:
    2
    Views:
    348
  3. Jp Calderone

    Re: lambda a plusieurs arguments

    Jp Calderone, May 27, 2005, in forum: Python
    Replies:
    1
    Views:
    391
  4. Steve Dogers

    lambda vs non-lambda proc

    Steve Dogers, Mar 30, 2009, in forum: Ruby
    Replies:
    1
    Views:
    178
    Sean O'Halpin
    Mar 30, 2009
  5. Haochen Xie
    Replies:
    4
    Views:
    245
    Haochen Xie
    Mar 17, 2013
Loading...

Share This Page