Prohibit generating of apply for case class

血红的双手。 提交于 2021-02-07 02:53:30

问题


I'm writing a type-safe code and want to replace apply() generated for case classes with my own implementation. Here it is:

import shapeless._

sealed trait Data
case object Remote extends Data
case object Local extends Data

case class SomeClass(){
  type T <: Data
}

object SomeClass {
  type Aux[TT] = SomeClass { type T = TT }
  def apply[TT <: Data](implicit ev: TT =:!= Data): SomeClass.Aux[TT] = new SomeClass() {type T = TT}
}

val t: SomeClass = SomeClass() // <------------------ still compiles, bad
val tt: SomeClass.Aux[Remote.type] = SomeClass.apply[Remote.type] //compiles, good
val ttt: SomeClass.Aux[Data] = SomeClass.apply[Data] //does not compile, good

I want to prohibit val t: SomeClass = SomeClass() from compiling. Is it possible to do somehow except do not SomeClass to be case class?


回答1:


There is a solution that is usually used if you want to provide some smart constructor and the default one would break your invariants. To make sure that only you can create the instance you should:

  • prevent using apply
  • prevent using new
  • prevent using .copy
  • prevent extending class where a child could call the constructor

This is achieved by this interesing patten:

sealed abstract case class MyCaseClass private (value: String)
object MyCaseClass {
  def apply(value: String) = {
    // checking invariants and stuff
    new MyCaseClass(value) {}
  }
}

Here:

  • abstract prevents generation of .copy and apply
  • sealed prevents extending this class (final wouldn't allow abstract)
  • private constructor prevents using new

While it doesn't look pretty it's pretty much bullet proof.

As @LuisMiguelMejíaSuárez pointed out this is not necessary in your exact case, but in general that could be used to deal with edge cases of case class with a smart constructor.




回答2:


So you can make the constructor private and ensure that T is also something different to Nothing.

I believe the best way to ensure the constructor is private (as well as many other things as @MateuszKubuszok show) is to use a (sealed) trait instead of a class:
(if you can not use a trait for whatever reasons, please refer to Mateusz's answer)

import shapeless._

sealed trait Data
final case object Remote extends Data
final case object Local extends Data

sealed trait SomeClass {
  type T <: Data
}

object SomeClass {
  type Aux[TT] = SomeClass { type T = TT }
  def apply[TT <: Data](implicit ev1: TT =:!= Data, ev2: TT =:!= Nothing): Aux[TT] =
    new SomeClass { override final type T = TT }
}

Which works like this:

SomeClass() // Does not compile.
SomeClass.apply[Remote.type] // Compiles.
SomeClass.apply[Data] // Does not compile.

You can see it running here.




回答3:


If you want to prohibit using some of auto-generated methods of a case class you can define the methods (with proper signature) manually (then they will not be generated) and make them private (or private[this]).

Try

object SomeClass {
  type Aux[TT] = SomeClass { type T = TT }
  def apply[TT <: Data](implicit ev: TT =:!= Data): SomeClass.Aux[TT] = new SomeClass() {type T = TT}
  private def apply(): SomeClass = ??? // added
}

val t: SomeClass = SomeClass() // doesn't compile
val tt: SomeClass.Aux[Remote.type] = SomeClass.apply[Remote.type] //compiles
val ttt: SomeClass.Aux[Data] = SomeClass.apply[Data] //doesn't compile

In principle, the methods (apply, unapply, copy, hashCode, toString) can be generated not by compiler itself but with macro annotations. Then you can choose any subset of them and modify their generation as you want.

Generate apply methods creating a class

how to efficiently/cleanly override a copy method

Also the methods can be generated using Shapeless case classes a la carte. Then you can switch on/off the methods as desired too.

https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/alacarte.scala

https://github.com/milessabin/shapeless/blob/master/core/src/test/scala/shapeless/alacarte.scala



来源:https://stackoverflow.com/questions/64453378/prohibit-generating-of-apply-for-case-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!