Issue #4601 has been updated by Nicos Gollan.


Yusuke Endoh wrote:

> But I don't like your approach.  It looks like an abuse of Proc#curry ;-)

It was just meant as an idea. You might call it positional currying, partial application, or whatever ;-)

Joshua Ballanco wrote:

> You can already do something similar with Method#to_proc and Proc#curry:

Your example shows the problem quite nicely, and that's what I am doing at the moment. It does not provide the functionality though.

> I've filed a different issue to ask about that behavior (#4610).

That looks like it'd be hard to use with splatting, but I guess for my requirements it would work something like:

 def foo(a, b = "bar", c="baz"); "#{a} #{b} #{c}"; end
 l = ->(m, a, b, c) {puts m.call(a, b, c)}
 c = l.curry
 c.(method(:foo)).('dog').().('cat')
 
and deal with the parameter-less calls through a mixture of the incoming parameters hash and Method#parameters.

When calling it "partial application", I think something like

 keep_me = method(:foo).apply(:a => 'dog', :c => 'cat')
 #=> unary proc/lambda/method

 # ...
 # 5 minutes later
 keep_me.call('bird')
 #=> "dog bird cat"
 
would still be useful in some places where you don't want complete evaluation, since you'd otherwise have to manually keep track of missing parameters until you can fill in the gaps.
----------------------------------------
Feature #4601: Re-ordering method parameters.
http://redmine.ruby-lang.org/issues/4601

Author: Nicos Gollan
Status: Open
Priority: Normal
Assignee: 
Category: 
Target version: 


In my attempt to make an old gem (merb-action-args) work with MRI 1.9.2, Rubinius, and other implementations that depend on Method#parameters to retrieve a description of parameters, I hit a roadblock (the core reason is that it is not possible to retrieve parameter defaults). To quote the gem's README:

== Original Problem: Out of order defaults

 class Foo < Merb::Controller
   def bar(one, two = "dos", three = "tres")
     "#{one} #{two} #{three}"
   end
 end

The interesting thing here is that hitting "/foo/bar?one=uno&three=three" will call foo("uno", "dos", "three"). In other words, the defaults can be in any order, and merb-action-args will figure out where to fill in the holes.

== Parameter Omission

This reordering feature means we need some way to omit parameters when calling a method, thus maintaining defaults. Originally, that was done by direct access to the AST, which is not really an option anymore with 1.9 and other VMs like Rubinius. One possible, but ugly syntax for straight omission would be:

 class Pet
   def quack(a, b='quack', c='woof')
     "#{a} #{b} #{c}"
   end
 end
 
 Pet.new.quack('meow', , 'whinny')  #=> "meow quack whinny"
 
Now, IMO, this looks quite ugly, and would be hard to make work with the ubiquitous *args pattern.

== Currying Extension

Another way to enable that behaviour would be parameter reordering, i.e., some way to map a Proc with n parameters to a method with >=n parameters. For the problem I am facing, that would mean I could curry the called method in a way so that all missing parameters are shifted to the end, and calling the resulting method with an appropriate list would preserve defaults. For example:

Assume the parameters passed to the quack action of the Pet controller are values for a and c. Then define a curried method:

 quack_proxy =
   pet_instance.method(:quack).curry(
     2,         # number of parameters in the resulting method
     [0, 2, 1]  # parameter order of the called method
   )
 
 # or alternatively use parameter names, omit defaults, ...:
 quack_proxy =
   pet_instance.method(:quack).curry(2, [:a, :c])
 
The resulting Proc quack_proxy would have the signature:

 # quack_proxy(a, c='woof', b='quack')
 
 quack_proxy.call(*['this', 'works'])
 # => "this quack works"
 

== Proposed Currying Syntax/Semantics


(Proc) (({Method#curry(num_args, order = nil)}))

Returns a proxy with num_args arguments to the receiver.

When order is nil, the parameters of the receiver are mapped to the first num_args parameters of the proxy in order.

When order is an Array of integers or symbols with order.size >= num_args, it describes the order in which the parameters of the proxy are mapped to the receiver. Required parameters not named or indexed are mapped in order of their appearance.

In cases where not all required parameters of the receiver are covered, an ArgumentError is raised.

Examples:

 def foo(req_a, req_b, opt_a=1, obt_b=2, opt_c=3); end
 m = Kernel.method(:foo)
 
 m.curry(2)
 # maps to foo(req_a, req_b) with all optional parameters at defaults
 
 m.curry(3, [0, 1, :opt_b])
 # maps to foo(req_a, req_b, opt_b=2) with optionals a and c at defaults
 
 m.curry(5, [0, 1, :opt_b])
 # maps to foo(req_a, req_b, opt_b=2, opt_a=1, opt_c=3)
 
 m.curry(1, [:req_b])
 # raises ArgumentError (not all required parameters covered)
 
 ret = m.curry(2, [:req_b])['hello']
 # maps to foo(req_a, 'hello') with all optionals at defaults
 # NOTE: this results in an unary method.
 
In the last example, the call to #curry creates a 2-ary Proc, which maps the first parameter to the req_b parameter of #foo, implicitly maps the second parameter to req_a, and hides all optional parameters. Then, by using #[] like Proc#[], the first parameter to the proxy is assigned. Since the second parameter is required, this returns an unary Proc.


-- 
http://redmine.ruby-lang.org