Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do you have to specify 2 arguments explicitly to curry :>

Tags:

ruby

Consider this, which works fine:

:>.to_proc.curry(2)[9][8] #=> true, because 9 > 8

However, even though > is a binary operator, the above won't work without the arity specified:

:>.to_proc.curry[9][8] #=> ArgumentError: wrong number of arguments (0 for 1)

Why aren't the two equivalent?

Note: I specifically want to create the intermediate curried function with one arg supplied, and then call then call that with the 2nd arg.

like image 213
Jonah Avatar asked Jan 29 '16 03:01

Jonah


People also ask

What does it mean to curry a function?

In mathematics and computer science, currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each takes a single argument.

How do curried functions work?

In other terms, currying is when a function — instead of taking all arguments at one time — takes the first one and returns a new function, which takes the second one and returns a new function, which takes the third one, etc. until all arguments are completed.


3 Answers

curry has to know the arity of the proc passed in, right?

 :<.to_proc.arity  # => -1

Negative values from arity are confusing, but basically mean 'variable number of arguments' one way or another.

Compare to:

 less_than = lambda {|a, b| a < b}
 less_than.arity # => 2

When you create a lambda saying it takes two arguments, it knows it takes two arguments, and will work fine with that style of calling #curry.

 less_than.curry[9][8] # => false, no problem!

But when you use the symbol #to_proc trick, it's just got a symbol to go on, it has no idea how many arguments it takes. While I don't think < is actually an ordinary method in ruby, I think you're right it neccessarily takes two args, the Symbol#to_proc thing is a general purpose method that works on any method name, it has no idea how many args the method should take, so defines the proc with variable arguments.

I don't read C well enough to follow the MRI implementation, but I assume Symbol#to_proc defines a proc with variable arguments. The more typical use of Symbol#to_proc, of course, is for a no-argument methods. You can for instance do this with it if you want:

hello_proc = :hello.to_proc
class SomeClass
  def hello(name = nil)
     puts "Hello, #{name}!"
  end
end
obj = SomeClass.new
obj.hello #=> "Hello, !"
obj.hello("jrochkind") #=> "Hello, jrochkind!"
obj.hello("jrochkind", "another")
# => ArgumentError: wrong number of arguments calling `hello` (2 for 1)

hello_proc.call(obj)  # => "Hello, !"
hello_proc.call(obj, "jrochkind") # => "Hello, jrochkind!"
hello_proc.call(obj, "jrochkind", "another")
# => ArgumentError: wrong number of arguments calling `hello` (2 for 1)

hello_proc.call("Some string")
# => NoMethodError: undefined method `hello' for "Some string":String

Note I did hello_proc = :hello.to_proc before I even defined SomeClass. The Symbol#to_proc mechanism creates a variable arity proc, that knows nothing about how or where or on what class it will be called, it creates a proc that can be called on any class at all, and can be used with any number of arguments.

If it were defined in ruby instead of C, it would look something like this:

class Symbol
  def to_proc
    method_name = self
    proc {|receiver, *other_args|  receiver.send(method_name, *other_args) }
  end
end
like image 196
jrochkind Avatar answered Oct 26 '22 14:10

jrochkind


I think it is because Symbol#to_proc creates a proc with one argument. When turned into a proc, :> does not look like:

->x, y{...}

but it looks like:

->x{...}

with the requirement of the original single argument of > somehow tucked inside the proc body (notice that > is not a method that takes two arguments, it is a method called on one receiver with one argument). In fact,

:>.to_proc.arity # => -1
->x, y{}.arity # => 2

which means that applying curry to it without argument would only have a trivial effect; it takes a proc with one parameter, and returns itself. By explicitly specifying 2, it does something non-trivial. For comparison, consider join:

:join.to_proc.arity # => -1
:join.to_proc.call(["x", "y"]) # => "xy"
:join.to_proc.curry.call(["x", "y"]) # => "xy"

Notice that providing a single argument after Currying :join already evaluates the whole method.

like image 24
sawa Avatar answered Oct 26 '22 14:10

sawa


@jrochkind's answer does a great job of explaining why :>.to_proc.curry doesn't have the behavior you want. I wanted to mention, though, that there's a solution to this part of your question:

I specifically want to create the intermediate curried function with one arg supplied, and then call then call that with the 2nd arg.

The solution is Object#method. Instead of this:

nine_is_greater_than = :>.to_proc.curry[9]
nine_is_greater_than[8]
#=> ArgumentError: wrong number of arguments (0 for 1)

...do this:

nine_is_greater_than = 9.method(:>)
nine_is_greater_than[8]
# => true

Object#method returns a Method object, which acts just like a Proc: it responds to call, [], and even (as of Ruby 2.2) curry. However, if you need a real proc (or want to use curry with Ruby < 2.2) you can also call to_proc on it (or use &, the to_proc operator):

[ 1, 4, 8, 10, 20, 30 ].map(&nine_is_greater_than)
# => [ true, true, true, false, false, false ]
like image 30
Jordan Running Avatar answered Oct 26 '22 14:10

Jordan Running