Why do case class companion objects extend FunctionN?
Asked Answered
D

4

38

When you create a case class, the compiler creates a corresponding companion object with a few of the case class goodies: an apply factory method matching the primary constructor, equals, hashCode, and copy.

Somewhat oddly, this generated object extends FunctionN.

scala> case class A(a: Int)                                 
defined class A

scala> A: (Int => A)
res0: (Int) => A = <function1>

This is only the case if:

  • There is no manually defined companion object
  • There is exactly one parameter list
  • There are no type arguments
  • The case class isn't abstract.

Seems like this was added about two years ago. The latest incarnation is here.

Does anyone use this, or know why it was added? It increases the size of the generated bytecode a little with static forwarder methods, and shows up in the #toString() method of the companion objects:

scala> case class A()
defined class A

scala> A.toString
res12: java.lang.String = <function0>

UPDATE

Manually created objects with a single apply method are not automatically considered as FunctionN:

object HasApply {
  def apply(a: Int) = 1
}
val i = HasApply(1)

// fails
//  HasApply: (Int => Int) 
Dysphonia answered 15/6, 2010 at 21:58 Comment(1)
That's a good question. Maybe it's so you can write something like: List(1,2) map A? I use this from time to time.Wimer
F
48

The reason why case class companion objects implement FunctionN is that before, case classes generated a class and a factory method, not a companion object. When we added extractors to Scala it made more sense to turn the factory method into a full companion object with apply and unapply methods. But then, since the factory method did conform to FunctionN, the companion object needed to conform, too.

[Edit] That said, it would make sense to have companion objects show as their own name, not as "function"

Francklin answered 16/6, 2010 at 14:25 Comment(1)
Ah, backwards compatibility, with some help from the uniform access principle. In that context, it makes sense. I've lodged an enhancement request to prettify the toString of these: lampsvn.epfl.ch/trac/scala/ticket/3579Dysphonia
G
11

Well, given that target.apply(a1, a2, a3 ... aN) in Scala:

  1. can be sugared by target(a1, a2, a3 ... aN)
  2. is the method which needs to be implemented by FunctionN

it seems natural that a companion object:

object MyClass {
  def apply(a1 : A1, ... aN: AN) = new MyClass(a1, ..., aN)
}

is really:

object MyClass extends FunctionN[A1, ... , AN, MyClass]{
  def apply(a1 : A1, ... aN: AN) = new MyClass(a1, ..., aN)
}

So the addition seems to be natural to me (I'm not sure why it seems "odd" to you?). As to whether it actually added anything; well, that is for someone smarter than me!

Galsworthy answered 15/6, 2010 at 22:38 Comment(0)
G
11

Aside from oxbow_lakes's reply about the naturalness of it, it can often be useful to have constructors available as first-class functions, particularly in conjunction with Scala collections higher-order functions. For (a trivial) example,

scala> case class Foo(i : Int)
defined class Foo

scala> List(1, 2, 3) map Foo   
res0: List[Foo] = List(Foo(1), Foo(2), Foo(3))
Goggles answered 16/6, 2010 at 9:37 Comment(1)
xs map Foo.apply is only marginally less convenient.Dysphonia
R
5
Welcome to Scala version 2.8.0.RC3 (Java HotSpot(TM) Client VM, Java 1.6.0_20).

scala> case class CC3(i: Int, b: Boolean, s: String)
defined class CC3

scala> CC3
res0: CC3.type = <function3>

scala> CC3.apply(1, true, "boo!")
res1: CC3 = CC3(1,true,boo!)

scala> CC3(1, true, "boo!")
res2: CC3 = CC3(1,true,boo!)
Ridley answered 15/6, 2010 at 22:19 Comment(1)
Well, the syntactic sugar of omitting the name of the apply is available regardless of whether or not the receiver extends FunctionN.Dysphonia

© 2022 - 2024 — McMap. All rights reserved.