Feature #4601
closedRe-ordering method parameters.
Description
=begin
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.
=end