Ruby: specify an argument until it turns into a block

We can easily define a method and turn it into a block with a unary ampersand.

def my_method(arg) puts arg*2 end ['foo', 'bar'].each(&method(:my_method)) # foofoo # barbar # or my_method = ->(arg) { puts arg*2 } ['foo', 'bar'].each(&my_method) # same output 

As we can see, the first argument is passed automatically when we work with aggregates. But what if we need to pass 2 or even more arguments?

 my_method = ->(arg,num) { puts arg*num } ['foo', 'bar'].each(&my_method) # ArgumentError: wrong number of arguments (1 for 2) ['foo', 'bar'].each(&my_method(3)) # NoMethodError: undefined method `foo' for main:Object ['foo','bar'].each do |i, &my_method| yield i, 3 end # LocalJumpError: no block given (yield) 

Is it possible to pass additional arguments while they turn proc into a block?

+7
ruby
source share
2 answers

Regarding your comment :

Strange, but it changes the arguments at runtime

Actually, the order of the arguments is preserved.

curry returns a new proc that efficiently collects arguments until there are enough arguments to invoke the original / proc method (based on its arity). This is achieved by returning intermediate processes:

 def foo(a, b, c) { a: a, b: b, c: c } end curried_proc = foo.curry #=> #<Proc:0x007fd09b84e018 (lambda)> curried_proc[1] #=> #<Proc:0x007fd09b83e320 (lambda)> curried_proc[1][2] #=> #<Proc:0x007fd09b82cfd0 (lambda)> curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3} 

You can pass any number of arguments at once to curries proc:

 curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3} curried_proc[1, 2][3] #=> {:a=>1, :b=>2, :c=>3} curried_proc[1][2, 3] #=> {:a=>1, :b=>2, :c=>3} curried_proc[1, 2, 3] #=> {:a=>1, :b=>2, :c=>3} 

Null arguments are ignored:

 curried_proc[1][][2][][3] #=> {:a=>1, :b=>2, :c=>3} 

However, you obviously cannot reorder the arguments.


An alternative to currying is a partial application that returns a new proc with lower arity, fixing one or more arguments. Unlike curry there is no built-in method for a partial application, but you can easily write your own:

 my_proc = -> (arg, num) { arg * num } def fix_first(proc, arg) -> (*args) { proc[arg, *args] } end fixed_proc = fix_first(my_proc, 'foo') #=> #<Proc:0x007fa31c2070d0 (lambda)> fixed_proc[2] #=> "foofoo" fixed_proc[3] #=> "foofoofoo" [2, 3].map(&fixed_proc) #=> ["foofoo", "foofoofoo"] 

Or fix the last argument:

 def fix_last(proc, arg) -> (*args) { proc[*args, arg] } end fixed_proc = fix_last(my_proc, 2) #=> #<Proc:0x007fa31c2070d0 (lambda)> fixed_proc['foo'] #=> "foofoo" fixed_proc['bar'] #=> "barbar" ['foo', 'bar'].map(&fixed_proc) #=> ["foofoo", "barbar"] 

Of course, you are not limited to fixing individual arguments. For example, you can return proc, which takes an array and converts it to an argument list:

 def splat_args(proc) -> (array) { proc[*array] } end splatting_proc = splat_args(my_proc) [['foo', 1], ['bar', 2], ['baz', 3]].map(&splatting_proc) #=> ["foo", "barbar", "bazbazbaz"] 
+1
source share

@sawa is right. You can do this with curry .

Program version:

 mult = proc {|a, b| a * b} # => #<Proc: 0x00000002af1098@ (irb):32> [1, 2].map(&mult.curry[2]) # => [2, 4] 

Method Version:

 def mult(a, b) a*b end [1, 2].map(&method(:mult).to_proc.curry[2]) # => [2, 4] 
+6
source share

All Articles