Lambda and multiple arguments: how?!

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

Hi --

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

···

On Wed, 29 Oct 2008, nvp wrote:

--
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!

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!

···

On Tue, Oct 28, 2008 at 1:36 PM, David A. Black <dblack@rubypal.com> wrote:

--
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

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[0...@code.arity]
    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[0...@code.arity]
    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

···

2008/10/28 nvp <ruby-lists@noopy.org>:

On Tue, Oct 28, 2008 at 1:36 PM, David A. Black <dblack@rubypal.com> 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?

--
remember.guy do |as, often| as.you_can - without end

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.

···

On Wed, Oct 29, 2008 at 6:41 AM, Robert Klemme <shortcutter@googlemail.com> wrote:

--
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