Case classes overriding defs

In Scala, we could use object to override val / 0-arity defs:

object emptyProduct extends Grad[HNil] {
   ...
 }

However, this does not extend to case classes. I would like that case classes could override defs with arity > 1 like this:

case class product[H, T <: HList](H: Grad[H], T: Grad[T]) extends Grad[H :: T] {
   ...
}

instead of

def product[H, T <: HList](H: Grad[H], T: Grad[T]): Grad[H :: T] = new Grad[H :: T] {
  ...
}

I would argue that the case class form looks better.

FYI: implicit classes can override defs.

Is this not because the case class itself extends Grad[H :: T], not its companion object? Presumably it doesn’t work for the same reason a (regular) class does not override a val.

I think this is an implementation detail, and it’s not defined in the spec. The fact that implicit classes are internally implemented with classes + implicit defs makes the synthesized implicit conversions override whatever was defined in the super class.

In my opinion, it looks really weird. Classes are not methods. I don’t want to override a method with a class, it looks like a first good step towards chaos!

However, I appreciate seeing suggestions from the Community, it’s been a while since we don’t get them :+1:.

The thing is that an object is a class and a value containing the singleton instance of that class. A case class is not a class and a method returning an instance of that class. It’s a class and a function returning an instance of that class.

This suggestion would actually make a lot of sense (or probably: would already be implemented) if functions and methods in Scala were unified.

1 Like

I wouldn’t say so. An object, language-wise, is a term. A class is a type (an entity to be more precise?). I don’t see how you can unify these two without breaking everyone’s mental model…

I would say an object defines a term as well as a type. But I agree you probably shouldn’t enable implementing methods with case classes. But I can see where he’s coming from.

For what it’s worth, this currently works for the reason pointed out by @Jasper-M:

trait A { type Foo; def Foo: Int => Foo }
object B extends A { case class Foo(x: Int) }

It breaks when type parameters are introduced, because the companion object to Foo no longer extends a function type (it would need to be a polymorphic function type).

Agreed—also, those two things aren’t equivalent. But I’m happy with writing case class Product and a forwarder override def product ... = Product(...).

In practice, this happens because case classes have the same meaning at the top-level and elsewhere, so the generated constructor can’t be (on the JVM) a top-level function, but must be a method on the companion object.

On the principle

But a case class isn’t just a type, it’s a type + a constructor method. In the spec, they’re classes and methods are both “templates”. That comes (IIUC) from the Beta family of languages, where classes and methods are unified more fully. There, the activation record of a method is nothing but a class, whose members are local definitions of the method, and the body of a constructor is a definition of the members of that class. The second part survives in Scala constructors, the rest is gone thanks to the JVM.

2 Likes