Scala Case Class Companion Objects - Conflict on the type name
Asked Answered
C

2

5

I am facing an issue with Companion Objects picking up its type instead of the case class

I am using spray json serdes. They need an implicit JsonFormat. This format is obtained by calling a function that depends on the number of parameters of the case class: jsonFormat2(Class2) if case class has two fields, like

case class Class2(a: String, b: Integer)

or jsonFormat3(Class3) for

case class Class3(a: String, b: Integer, c: Long)

Given that having to know the number of params your case class have throughout the code is not nice, I wanted to make a case class companion object so you can encapsulate this info and get the JsonFormat from the class itself, like:

object Class2 extends DefaultJsonProtocol 
{
    def getJsonFormat() = {
        jsonFormat2(Class2)
    }
}

But if I do so, I'll get the following compilation issue:

type mismatch;
[error]  found   : mypackage.Class2.type
[error]  required: (?, ?) => ?
[error]     jsonFormat2(Class2)

If we look at the code in jsonFormat2, the signature is:

def jsonFormat2[P1 :JF, P2 :JF, T <: Product :ClassManifest
    (construct: (P1, P2) => T): RootJsonFormat[T] = { // ... 

If I change the companion object name (e.g. to MyClass2) it will just work. So, it seems types are conflicting.

It seems like when dealing with typing, companion objects would not be able to be named like the class they go with.

Could somebody please explain why this is happening, if there is a limitation, or otherwise how to work it around, so companion object can be used with the same name?

Cosy answered 2/3, 2018 at 17:0 Comment(1)
Did you mean jsonFormat2(Class2.apply)?Avestan
A
4

Your definition of object Class2 does not extend (String, Integer) => Class2. You might want to pass the apply-method explicitly:

case class Class2(a: String, b: Integer)

object Class2 extends DefaultJsonProtocol 
{
    def getJsonFormat() = {
        jsonFormat2(Class2.apply)
    }
}

Inspired by @AlexeyRomanov's helpful comment, I decided to add a slightly more detailed explanation why it works when you don't define a companion object, and why it stops working when you define it as object Class2 { ... }.

If you compile

class Foo(n: Int, s: String)

then the decompiled code of the automatically generated companion object looks as follows:

public final class Foo$
extends AbstractFunction2<Object, String, Foo>
implements Serializable {

    /* some lines omitted */

    public Foo apply(int n, String s) {
        return new Foo(n, s);
    }

    /* some lines omitted */
}

that is, it extends AbstractFunction2, and therefore conforms to (?, ?) => ?.

When you define object Foo on your own, the generated object does not extends (Int, String) => Foo. This is why it stops working when you define a companion object on your own without extending Function.

Avestan answered 2/3, 2018 at 17:15 Comment(2)
"If you invoke jf2(Foo), it takes the constructor Foo as the two-argument method, and everything is fine." This is incorrect, it takes the companion object.Kielty
@AlexeyRomanov Indeed, you are right. I thought it desugars the constructor automatically into jf2(new Foo(_, _)), but that's not the case. I've retracted my flawed theory about the constructor, and retained only the (hopefully useful) conclusion.Avestan
K
5

When a companion object for Class2 is defined implicitly, it extends (String, Integer) => Class2; your version doesn't. If you change to

object Class2 extends DefaultJsonProtocol with (String, Integer) => Class2 { ... }

it will work, but to avoid duplicating parameter types I'd go with Andrey Tyukin's suggestion (even if the explanation is incorrect).

Kielty answered 2/3, 2018 at 17:53 Comment(1)
Thanks for your replies. I accepted Tyukin's answer because it offers a more complete explanation. I upvoted yours and your comment too.Cosy
A
4

Your definition of object Class2 does not extend (String, Integer) => Class2. You might want to pass the apply-method explicitly:

case class Class2(a: String, b: Integer)

object Class2 extends DefaultJsonProtocol 
{
    def getJsonFormat() = {
        jsonFormat2(Class2.apply)
    }
}

Inspired by @AlexeyRomanov's helpful comment, I decided to add a slightly more detailed explanation why it works when you don't define a companion object, and why it stops working when you define it as object Class2 { ... }.

If you compile

class Foo(n: Int, s: String)

then the decompiled code of the automatically generated companion object looks as follows:

public final class Foo$
extends AbstractFunction2<Object, String, Foo>
implements Serializable {

    /* some lines omitted */

    public Foo apply(int n, String s) {
        return new Foo(n, s);
    }

    /* some lines omitted */
}

that is, it extends AbstractFunction2, and therefore conforms to (?, ?) => ?.

When you define object Foo on your own, the generated object does not extends (Int, String) => Foo. This is why it stops working when you define a companion object on your own without extending Function.

Avestan answered 2/3, 2018 at 17:15 Comment(2)
"If you invoke jf2(Foo), it takes the constructor Foo as the two-argument method, and everything is fine." This is incorrect, it takes the companion object.Kielty
@AlexeyRomanov Indeed, you are right. I thought it desugars the constructor automatically into jf2(new Foo(_, _)), but that's not the case. I've retracted my flawed theory about the constructor, and retained only the (hopefully useful) conclusion.Avestan

© 2022 - 2024 — McMap. All rights reserved.