Perf difference between currying and returning classes

Does currying have the same performance cost of a function which constructs and returns a callable object to result in the same semantics as currying?

def f(a: Int)(b: Int) = a + b
f(1)(2) == 3

versus

class Callable(a: Int):
  def apply(b: Int): Int =
     a + b
      
def f(a: Int) = Callable(a)

f(1)(2) == 3

They should have a cost difference.
Since, the first one is, AFAIK, just sugar for a method of two parameters.
Whereas, the second one will require instantiating a class.

But depending on how they are used, and if you apply some magic like inline then both could be reduced to the same bytecode, or both could end up being optimized by the JIT.

2 Likes

How is this possible, since I am allowed to partially apply the curry?

def f(a: Int)(b: Int) = a + b
val g = f(1) // this must be an object, right?

g(2) == 3

Syntactically there’s a difference but in the bytecode it’s the same as

def f(a: Int, b: Int) = a + b
val g = f(1, _)

g(2) == 3
1 Like

So this sounds like you’re saying it is identical performance, requires allocating objects on the heap just as much?

That is what I meant by: “But depending on how they are used”.

Yes, if you just partially apply them both to later on call them, then in theory both will allocate something.
However, considering functions are then handled using the invokeDynamic I still think the curry version would be more optimized, but I could be wrong. And it probably also depends if “later on” is just the next line or if they are stored for later, if they are reused, etc.

As with anything performance-related, the only good answer is that if you really care then you need to analyze and benchmark it yourself.