How do I create an explicit companion object for a case class which behaves identically to the replaced compiler provided implicit companion object?
Asked Answered
C

3

10

I have a case class defined as such:

case class StreetSecondary(designator: String, value: Option[String])

I then define an explicit companion object:

object StreetSecondary {
  //empty for now
}

The act of defining the explicit companion object StreetSecondary causes the compiler produced "implicit companion object" to be lost; i.e. replaced with no ability to access the compiler produced version. For example, the tupled method is available on case class StreetSecondary via this implicit companion object. However, once I define the explicit companion object, the tupled method is "lost".

So, what do I need to define/add/change to the above StreetSecondary explicit companion object to regain all the functionality lost with the replacement of the compiler provided implicit companion object? And I want more than just the tupled method restored. I want all functionality (for example, including extractor/unapply) restored.

Thank you for any direction/guidance you can offer.


UPDATE 1

I have done enough searching to discover several things:

A) The explicit companion object must be defined BEFORE its case class (at least that is the case in the Eclipse Scala-IDE WorkSheet, and the code doesn't work in the IntelliJ IDE's WorkSheet regardless of which comes first).

B) There is a technical trick to force tupled to work (thank you drstevens): (StreetSecondary.apply _).tupled While that solves the specific tupled method problem, it still doesn't accurately or completely describe what the scala compiler is providing in the implicit companion object.

C) Finally, the explicit companion object can be defined to extend a function which matches the signature of the parameters of the primary constructor and returns an instance of the case class. It looks like this:

object StreetSecondary extends ((String, Option[String]) => StreetSecondary) {
  //empty for now
}

Again, I am still not confident accurately or completely describes what the scala compiler is providing in the implicit companion object.

Chute answered 19/8, 2014 at 20:28 Comment(4)
Here's a related post explaining why the compiler generated implicit companion object extends FunctionN: #3049868Chute
According to #34534039, the "implicit companion object" isn't lost, but has the explicitly-defined companion object sort of "appended" or "combined". (I don't know which is correct :/ )Blab
@Blab Unfortunately it is partially lost. Try yourself with case class Foo(a: Any, b: Any) {}; object Foo {}; Foo.tupled leads to a compiler error. Take out the object and it works.Allin
There's a bug ticket for Scala to fix this. It's 6 years old, maybe one day it will be fixed and the explicit companion object does not cause the implicit companion object to be lost.Allin
C
5

When defining an explicit companion object for a case class (as of Scala 2.11), to fully replace the compiler provided functionality in the lost implicit companion object, the basic template for the explicit companion object has two requirements:

Requirements:
1. Must extend a function definition which consists of a tuple (exactly matching the type and order of the case class constructor parameters) returning the type of the case class
2. Must override the toString function to provide the object class name (identical to that of the associated case class)

Here's the original example code for the "empty" explicit companion object:

object StreetSecondary {
  //empty for now
}

And here is the example code after implementing the above requirements:

object StreetSecondary extends ((String, Option[String]) => StreetSecondary) {
    //replace the toString implementation coming from the inherited class (FunctionN)
    override def toString =
      getClass.getName.split("""\$""").reverse.dropWhile(x => {val char = x.take(1).head; !((char == '_') || char.isLetter)}).head
}

To meet requirement 1 above, extends ((String, Option[String]) => StreetSecondary) is inserted right after the object name and before the first curly brace.

To meet requirement 2 above, override def toString = getClass.getName.split("""\$""").reverse.dropWhile(x => {val char = x.take(1).head; !((char == '_') || char.isLetter)}).head is inserted in the body of the object (the explicit implementation remains questionable)

Deep appreciation to @drstevens for his posting the javap output to help me gain confidence the above two steps are all that are required to restore the lost functionality.

Chute answered 20/8, 2014 at 20:45 Comment(3)
I'm not sure all the boiler plate is worth getting back whatever is lost. The important functions, unapply, apply, equals, hashCode, toString are not lost. Based on Odersky's answer to the question you found (#3049868), it only exists for legacy reasons. The psudeo-specialized tuple function is nice to have, but again, it's a function belonging to Function2 anyways. I definitely learned something from this exercise though. In the 3 years I've been writing Scala, I've never come across this.James
@James I agree. I am not sure it is worth the boilerplate, either. What I needed was the confidence that when I decided to move a case class from depending upon a scala compiler implicit companion object to an explicit companion object, existing code depending upon the implicit functionality continued to work. I had been using the tupled method interacting with transforming text files. So, it was startling to have it just "disappear" when I went to refactor the code producing an explicit companion object.Chute
I've continued down and extended this pathway with a post at CodeReview which enables a validation and instantiation pattern without throwing RuntimeExceptions (returns a List of them in an Either to facilitate a more FP way of handling creating case classes): codereview.stackexchange.com/a/60756/4758Chute
J
3

Scala 2.11.0

It looks like scalac is predefining the tupled function when you aren't providing additional functions in the companion

case class BB(a: Int, b: Int)
object BB { }

case class AA(a: Int, b: Int)

object CC { }
case class CC(a: Int, b: Int)

results in the following

public class AA implements scala.Product,scala.Serializable {
  public static scala.Option<scala.Tuple2<java.lang.Object, java.lang.Object>> unapply(AA);
  public static AA apply(int, int);
  public static scala.Function1<scala.Tuple2<java.lang.Object, java.lang.Object>, AA> tupled();
  public static scala.Function1<java.lang.Object, scala.Function1<java.lang.Object, AA>> curried();
  public int a();
  public int b();
  public AA copy(int, int);
  public int copy$default$1();
  public int copy$default$2();
  public java.lang.String productPrefix();
  public int productArity();
  public java.lang.Object productElement(int);
  public scala.collection.Iterator<java.lang.Object> productIterator();
  public boolean canEqual(java.lang.Object);
  public int hashCode();
  public java.lang.String toString();
  public boolean equals(java.lang.Object);
  public AA(int, int);
}

public final class AA$ extends scala.runtime.AbstractFunction2<java.lang.Object, java.lang.Object, AA> implements scala.Serializable {
  public static final AA$ MODULE$;
  public static {};
  public final java.lang.String toString();
  public AA apply(int, int);
  public scala.Option<scala.Tuple2<java.lang.Object, java.lang.Object>> unapply(AA);
  public java.lang.Object apply(java.lang.Object, java.lang.Object);
}

public class BB implements scala.Product,scala.Serializable {
  public static scala.Option<scala.Tuple2<java.lang.Object, java.lang.Object>> unapply(BB);
  public static BB apply(int, int);
  public int a();
  public int b();
  public BB copy(int, int);
  public int copy$default$1();
  public int copy$default$2();
  public java.lang.String productPrefix();
  public int productArity();
  public java.lang.Object productElement(int);
  public scala.collection.Iterator<java.lang.Object> productIterator();
  public boolean canEqual(java.lang.Object);
  public int hashCode();
  public java.lang.String toString();
  public boolean equals(java.lang.Object);
  public BB(int, int);
}

public final class BB$ implements scala.Serializable {
  public static final BB$ MODULE$;
  public static {};
  public BB apply(int, int);
  public scala.Option<scala.Tuple2<java.lang.Object, java.lang.Object>> unapply(BB);
}

public class CC implements scala.Product,scala.Serializable {
  public static scala.Option<scala.Tuple2<java.lang.Object, java.lang.Object>> unapply(CC);
  public static CC apply(int, int);
  public int a();
  public int b();
  public CC copy(int, int);
  public int copy$default$1();
  public int copy$default$2();
  public java.lang.String productPrefix();
  public int productArity();
  public java.lang.Object productElement(int);
  public scala.collection.Iterator<java.lang.Object> productIterator();
  public boolean canEqual(java.lang.Object);
  public int hashCode();
  public java.lang.String toString();
  public boolean equals(java.lang.Object);
  public CC(int, int);
}

public final class CC$ implements scala.Serializable {
  public static final CC$ MODULE$;
  public static {};
  public CC apply(int, int);
  public scala.Option<scala.Tuple2<java.lang.Object, java.lang.Object>> unapply(CC);
}
James answered 19/8, 2014 at 22:0 Comment(4)
Awesome! Tysvm! As this is Java code, it's going to take me some time to parse/evaluate/diff this out to figure out what it looks like in Scala. How did you generate this?!Chute
Put the scala code in a file and compile it with scalac, e.g. scalac testing.scala. Then you can use javap to look at the generated class files, e.g. javap AA$.James
I have just posted an answer detailing what must be done in Scala code to duplicate the functionality provided by the implicit companion object to a case class. If you have time and inclination, please review it to see if I have missed anything obvious. Thank you.Chute
In more recent versions of Scala, you can also get (only verbose, including the bytecode instructions) the javap dump of a class in the REPL with :javap SomeClassCoralloid
C
1

Why would you think you are loosing them?

case class Person(name: String, age: Int)
object Person {
  def apply(): Person = new Person("Bob", 33)
}

val alice = Person("Alice", 20)
val bob   = Person()

Person.unapply(alice)    //Option[(String, Int)] = Some((Alice,20))
Person.unapply(Person()) //Option[(String, Int)] = Some((Bob,33))

Seems like I still got extractors.

In your case you still got it all:

scala> StreetSecondary.unapply _
res10: StreetSecondary => Option[(String, Option[String])] = <function1>
Cicatrize answered 19/8, 2014 at 21:5 Comment(6)
Does the tupled method work? I get a red squiggly under tupled when attempting to use it. For more context, see StreetSecondary3 in this question which is where I was having the issue with the tupled method: #25386993Chute
There is actually no tupled. True. Wasn't aware of that. I haven't used this yet.Cicatrize
@Chute tupled is a function on Function2 or (A, B) => C. Try (StreetSecondary.apply _).tupled. Not sure why the compiler is no longer able to infer this.James
I think it won't work as soon as we add new constructor in companion object. Compiler probably won't know which apply method call and that's why it can't infer tupledCicatrize
@Cicatrize That's exactly why I want clarification on explicitly what the scala compiler is "generating" when it creates the implicit companion object. For example, I am pretty sure that the compiler adds "extends ((String, Option[String]) => StreetSecondary)" right after the implicit companion object name. This is where the tupled method is located. I need to know what the full definition is for the implicit companion object so I can be sure I am providing it when I explicitly generate an explicit companion object. I have googled pretty hard to find the answer and thus far, no joy.Chute
@James That worked (although I don't understand how). However, I'm not really trying to solve the tupled method missing problem per se. I am trying to figure out much more accurately and completely what the scala compiler is generating when it auto-generates the implicit companion object. At this point, I cannot find a specification, formal or informal, anywhere explicitly outlining this.Chute

© 2022 - 2024 — McMap. All rights reserved.