2

Ruby 1.9's built in support of currying supports two ways to deal with a proc taking an arbitrary number of arguments:

my_proc = proc {|*x| x.max } 

1) curry without arguments: my_proc.curry. You pass the comma-separated arguments to the curried proc like you would to the normal proc. This does not achieve proper currying if the number of arguments is arbitrary (it is useful if some of the arguments are not splat)

2) curry with arguments: my_proc.curry(n) This way, the currying gets applied as if the proc would take n arguments. For example:

my_proc.curry(3).call(2).call(5).call(1) #=> 5 

So, how would you achieve currying with an arbitrary number of arguments? That means, if n is not given?

One way that comes into my mind is to collect the arguments via proxying call and then resolve the proc via method_missing (if any method other than call is used / call is used without arguments, call the proc with the collected arguments), but I'm still looking for other ways to achieve it.

Update

As Andy H stated, the problem is when to stop currying. For my purposes, it would be ok if the currying stops / the proc evaluates when either any method besides call is invoked or call is invoked without arguments.

4
  • Proc cannot take an indefinite number of arguments (although it can take an arbitrary number of arguments). Commented Feb 7, 2013 at 16:23
  • @sawa that's what I meant, thank you. Corrected the question. Commented Feb 7, 2013 at 16:36
  • Sorry, could you reformulate your question? I do not understand what is your problem. You can write .curry(5), .curry(7), curry(1_000_000), as you wish. Commented Feb 7, 2013 at 18:15
  • @BorisStitnicky I reformulated the question, I hope it is clear now. Commented Feb 8, 2013 at 8:34

3 Answers 3

7

The built-in curry method won't work for you. The reason is that it produces a proc that evaluates as soon as it has enough arguments to do so. Quoting the doc you linked to:

A curried proc receives some arguments. If a sufficient number of arguments are supplied, it passes the supplied arguments to the original proc and returns the result.

The key point to realise here is that zero is a "sufficient number of arguments" for a splat parameter.

f = ->(x, y, z, *args){ [x, y, z, args] } g = f.curry # => a "curryable" proc h = g.call(1) # => another curryable proc i = h.call(2) # => another curryable proc j = i.call(3) # => [1, 2, 3, []] 

As you show, these "curryable" procs are can be passed their arguments one-at-a-time, each time returning a new curryable proc until sufficient arguments have been passed, at which point it evaluates. This is also why they couldn't support arbitrary-length argument lists - how would it know when to stop currying and just evaluate?

If you wanted a different way of currying that allowed arbitrary numbers of arguments, you could define your own curry method:

def my_curry(f, *curried_args) ->(*args) { f.call(*curried_args, *args) } end 

This is a fairly simplistic implementation, but might serve your purposes. The key differences to the built-in method are that it always returns a new proc, even if sufficient arguments have been passed, and it doesn't support the one-at-a-time "curry chaining".

f = ->(x, y, z, *args) { [x, y, z, args] } g = my_curry(f, 1) # => a proc g.call(2, 3) # => [1, 2, 3, []] g.call(2, 3, 4, 5) # => [1, 2, 3, [4, 5]] g.call(2) # => ArgumentError: wrong number of arguments (2 for 3) h = my_curry(g, 2, 3) # => a proc h.call # => [1, 2, 3, []] h.call(4, 5) # => [1, 2, 3, [4, 5]] 
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your answer. As you said, the problem with currying and arbitrary arguments is when to stop currying and evaluate. For my purposes, it would be ok if the currying stops / the proc evaluates when either 1) any method besides call is invoked, or 2) call is invoked without arguments. I'm looking for an elegant way to do that. But +10 from me for the detailed answer.
2

I hate to be humpfhoxious, but how about forget curry and just write

my_proc = proc { |*x| x.max } l = -> *x { my_proc.( 4, 6, 12, 1, *x ) } l.call + 1 #=> 13 

You know, Matz himself said that curry is "just a toy for functional kids". Its implementation is not special, it is not faster or different from what you see above. Curry is not that useful in Ruby...

And for those who do not believe what I say, here is the exact quote and link:

Easter Egg for Functional Programming Kids

2 Comments

I'd like to see that quote in the internet somewhere not stated by you. If you do not see any sense in currying, just leave it alone. Your answer is not in any way providing something helpful.
Edited with link to the original discussion. Obviously, the BDFL does not think that with curry, he is providing anything extra helpful either.
0

I realized a solution using a subclass of Basic Object as a wrapper to do currying:

class CurryWrap < BasicObject def initialize proc @proc = proc @args = [] return self end def call *args if args.size == 1 @args << args.first return self elsif args.empty? return @proc.call(*@args) else @proc.call(*@args).call(*args) end end def == other @proc.call(*@args) == other end def method_missing m, *args, &block @proc.call(*@args).send(m, *args, &block) end end 

With this, one can basically do the following:

my_proc = proc { |*x| x.max } CurryWrap.new(my_proc).call(4).call(6).call(12).call(1) + 1 #=> 13 

Anything besides call with an argument will resolve the proc.

I don't know if this is the most elegant way to do this though, so I'll leave the answer unaccepted for now.

2 Comments

So here you basically treat any message other than #call as a signal to stop currying. That leaves me wondering – wouldn't it be better to use an explicit signal for that?
@BorisStitnicky Depends on the case in my opinion. If you can apply anything else to resolve the proc, you also will be able to apply anything without currying, so the sense of currying goes away. So the thing is for the proc to resolve itself once it knows that there are no more arguments coming its way.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.