Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

explicit case class companion does not extend Function / override toString #3664

Closed
scabug opened this issue Jul 12, 2010 · 17 comments · Fixed by scala/scala#10648
Closed

explicit case class companion does not extend Function / override toString #3664

scabug opened this issue Jul 12, 2010 · 17 comments · Fixed by scala/scala#10648
Assignees
Milestone

Comments

@scabug
Copy link

scabug commented Jul 12, 2010

When explicitly defining the companion object of a case class, the compiler does not add a FunctionN parent and does not override toString.

working as expected:

scala> object u { case class C(x: Int) }
defined module u

scala> u.C
res1: u.C.type = C

scala> val f: Int => u.C = u.C
f: (Int) => u.C = C

scala> 

unexpected:

scala> object t { object C; case class C(x: Int) }
defined module t

scala> t.C
res0: t.C.type = t$$C$$@5e58a983

scala> val f: Int => t.C = t.C
<console>:6: error: type mismatch;
 found   : t.C.type (with underlying type object t.C)
 required: (Int) => t.C
       val f: Int => t.C = t.C
                             ^
@scabug
Copy link
Author

scabug commented Jul 12, 2010

Imported From: https://issues.scala-lang.org/browse/SI-3664?orig=1
Reporter: @lrytz

@scabug
Copy link
Author

scabug commented Jan 31, 2012

@milessabin said:
Is this really "Not a Bug"? Can someone point to specage which justifies that resolution?

@scabug
Copy link
Author

scabug commented Jan 31, 2012

@lrytz said:
inconsistent with the spec - it does not say that generated companions extend from Function

@scabug
Copy link
Author

scabug commented Dec 3, 2012

Fred (fred) said:
What's the status on this?

This one is annoying for me when trying to use the new "JSON Inception" coming in playframework 2.1 (http://mandubian.com/2012/11/11/JSON-inception/).

Consider this using play 2.1-RC1 and scala 2.10.0-RC1:

import play.api.libs.json._
import play.api.libs.functional.syntax._

case class ThisIsOk(id: String)

case class ThisIsNotOk(id: String)

object ThisIsNotOk {
  val something = 2
}

case class ThisBecomesOk(id: String)

object ThisBecomesOk extends (String => ThisBecomesOk) {
  val something = 2
}

object Test {
  val compilesFine = Json.format[ThisIsOk]

/*
Compilation error:
[error]  found   : ThisIsNotOk.type
[error]  required: String => ThisIsNotOk
[error]     val doesNotCompile = Json.format[ThisIsNotOk]
*/
  val doesNotCompile = Json.format[ThisIsNotOk]

  val compilesFineButIsAPain = Json.format[ThisBecomesOk]
}

@scabug
Copy link
Author

scabug commented Sep 9, 2015

@hseeberger said:
So, what's the status here? It's very disappointing that one can't use tupled for case classes which have an explicit companion.

@scabug
Copy link
Author

scabug commented Sep 9, 2015

Jeff May (jeffmay) said:
Yea, this is one of those "odd Scala bugs" that I have to explain to newcomers all the time. We have a lot of code that was written before switching to Play Json, and a lot of places just passed the case class instead of the lambda'd apply method. The newcomer will add a companion object to create the Json Format and all of a sudden get compile errors about companion object singleton types not extending blah blah blah. It's definitely unexpected.

I notice that a lot of people I work with don't really know the difference between f(X(_, _)) or f(X *) or f(X.apply *) or f(X.apply) or f(X) and certainly don't understand singleton types. Its a very subtle and confusing area already and this bug just adds to the exceptional cases. It would be nice to see some consistency here. Either case class companion objects should be functions or they shouldn't. The fact that the true behavior is a surprise seems un Scala-like considering much of the language just does the right thing when mixing a lot of orthogonal features.

@scabug
Copy link
Author

scabug commented Sep 9, 2015

Stephen Compall (s11001001) said:
Heiko, I agree, but you can work around it by adding extends function type to the object.

@scabug
Copy link
Author

scabug commented Sep 9, 2015

Jeff May (jeffmay) said:
The other work around is to f(X.apply.tupled) or f((X.apply _).tupled) I am not sure which right now.

My issue with this is that it is inconsistent. I wouldn't mind typing the work around of using the apply method every time. The fact that the rules change for what seems like an unrelated change is what bothers me.

@scabug
Copy link
Author

scabug commented Dec 16, 2016

Sven Ludwig (sourcekick) said:
I just stumbled over this starting with Slick, where this is also referenced in the docs: http://slick.lightbend.com/doc/3.2.0-M2/schemas.html#mapped-tables together with #4808

It would be cool to see this fixed for a better "just works out-of-the-box" feeling.

@HuStmpHrrr
Copy link

hi all, yet another case. this is a fundamental mistake, no? we either shouldn't inherit FunctionX or inherit it automatically, shouldn't we? this looks entirely inconsistent.

@S11001001
Copy link

@HuStmpHrrr It's hard to say. It's inconsistent, and a frequent gotcha, but the obvious solutions have problems.

Never extend function type: Well, this is just piling on the inconvenience. Thanks but no thanks.

Extend function type even when explicitly defining object: This is the most intuitive behavior, and probably what you want 95% of the time. But it's not all sunshine and roses: what if you don't want to extend the function type? What if you want to extend the function type, but at different type parameters?

case class Foo(b: Bar)

object Foo extends (Baz => Foo) {
  def apply(bz: Baz): Foo = apply(bazToBar(bz))
}

This is a direct example, but you might also be extending the function type indirectly, via extending some other trait you wish your object to extend. This would not compile if the case class machinery insisted on a Bar => Foo superclass (depending on Bar/Baz's subtyping relationship, of course).

It just so happens that, in the absence of any syntax saying you don't want the function extend, the syntax for saying so is to declare the object explicitly without the function extend. So the 5% case is the default, the 95% case is more verbose, but at least you can do everything you might need to.

@som-snytt
Copy link

One proposal to signal don't extend FunctionN is to supply a private apply with the matching signature. A linter for unused members would probably want to ignore that use case.

leo-da added a commit to digital-asset/daml that referenced this issue Feb 26, 2020
so JsonError can be used instead of JsonError.apply
mergify bot pushed a commit to digital-asset/daml that referenced this issue Feb 26, 2020
* Extract ErrorOps, use liftErr instead of leftMap
JSON error formatting cleanup

CHANGELOG_BEGIN
CHANGELOG_END

* Good we have tests for this stuff

* Apply scala/bug#3664 work-around,

so JsonError can be used instead of JsonError.apply

* error formatting
@hmemcpy
Copy link

hmemcpy commented Apr 16, 2020

I just ran into this by attempting to use tupled:

case class Person(name: String, age: Int)
val p = ("Abe", 42)

Calling:
Person.tupled(p) works when there's no companion object for Person.

Would really love to see this fixed.

@HuStmpHrrr
Copy link

wow this issue is still open.

one potential fix I can come up with is to add a piece of syntax so that intention is revealed properly. for example,

case class Person(name: String, age: Int)
object Person {
// something
}

this creates a Person companion object which does not have any builtin function etc.

imagine the syntax is augmented:

case class Person(name: String, age: Int)
extend object Person {
// something
}

in this imaginary case, the something inside is added into the companion object.

in general, the fact that we are not able to add additional functionality to companion objects is quite absurd imo.

@Jasper-M
Copy link
Member

Jasper-M commented Apr 16, 2020

the fact that we are not able to add additional functionality to companion objects is quite absurd

But you can. Everything that you write in the body of object Person { ... } is effectively added to the Person companion object in addition to the generated apply and unapply methods (or instead of, in case of clashes). Only the FunctionN supertype is not automatically added.

The weird thing is this:

scala> case class Foo(); object Foo
class Foo
object Foo

scala> Foo.isInstanceOf[Serializable]
val res4: Boolean = true

scala> object Bar // not a companion object
object Bar

scala> Bar.isInstanceOf[Serializable]
val res5: Boolean = false

The Serializable supertype is added to the custom companion object. Wouldn't it be possible to also automatically add the FunctionN parent iff the custom companion object still has a public compatible apply method?

@HuStmpHrrr
Copy link

I guess @hmemcpy just mentioned that tupled doesn't work? probably only apply and unapply are special?

there is a good reason to believe these two functions are special too.

in any case, it's always good to have syntax to distinguish intentions, otherwise once it's "fixed", someone else is going to complain.

@som-snytt
Copy link

Folks were still commenting here a year after the matter was settled at scala/scala3#6190.

Note that the companion extended runtime.AbstractionFunction, which matters for N <= 2 because of specialization.

It would be or have been nice to balk only at mixing in traits where it is expensive, imposing the parent class where feasible.

The dotty solution includes eta-expanding C.apply.

@SethTisue SethTisue modified the milestones: Backlog, 2.13.13 Feb 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants