5

Suppose I have a case class below

case class SomeCaseClass[M] private (
  value: String
) 

and in another file, I have the following trait and object.

trait SomeTrait[A] {
  def get(oldId: String): A
   :
}

object SomeObject {
  private[this] def init[A](): SomeTrait[A] = new SomeTrait[A] {
    def get(oldId: String): A = id(oldId)
     :
  }

  val aaa: SomeTrait[String] = init[String]()
  val bbb: SomeTrait[SomeCaseClass[String]] = init[SomeCaseClass[String]]()
}

How should I modify the code so that restrict the init method only to being used with SomeCaseClass[_] type and not with any types like String as above?

Ideally with some modification to the code, the line val aaa: SomeTrait[String] = init[String]() should cause compilation error.

d-_-b
  • 4,142
  • 6
  • 28
  • 43

3 Answers3

6

This is what I came up with:

case class SomeCaseClass[M] private (
  value: String
) 

trait SomeTrait[A] {
  def get(oldId: String): A
}

private[this] def init[A <: SomeCaseClass[_]](): SomeTrait[A] = new SomeTrait[A] {
  def get(oldId: String): A = ???
}

val aaa: SomeTrait[String] = init[String]() // Will fail
val bbb: SomeTrait[SomeCaseClass[String]] = init[SomeCaseClass[String]]()

It fails with

ScalaFiddle.scala:16: error: type arguments [String] do not conform to method init's type parameter bounds [A <: ScalaFiddle.this.SomeCaseClass[_$1] forSome { type _$1 }]

You can check this scalafiddle.

I do not know if this is the best approach, but init[A <: SomeCaseClass[_]] is adding a type bound to A, and forcing A to be a Subclass of SomeCaseClass. I would love to know if there is a better way though.

Alejandro Alcalde
  • 5,990
  • 6
  • 39
  • 79
5

You can force a type parameter to be equal to some type B by using an implicit parameter:

def foo[A](implicit e: A =:= B): …

Also see this question.

To add some more value to this answer. Following code shows how to use the implicit parameter e: A =:= String to convert an A to a String.

def bar(b: String): Unit = println(b)
def foo[A](a: A)(implicit e: A =:= String): Unit = {
  bar(e(a))
}

foo("hi")  //compiles

foo(5)     //error: Cannot prove that scala.this.Int =:= String.

Answer to problem the OP has

This problem is much simpler: Make the method parametric only in the parameter A of SomeCaseClass[A], instead of using the whole type SomeCaseClass[A] as a type parameter:

private[this] def init[A](): SomeTrait[SomeCaseClass[A]] = new
  SomeTrait[SomeCaseClass[A]] {
    def get(oldId: String): SomeCaseClass[A] = ???
  }
ziggystar
  • 28,410
  • 9
  • 72
  • 124
  • Din't knew of `=:=`. To be precise with the OP, this is the closest solution, I think. – Alejandro Alcalde May 02 '19 at 14:05
  • Could you provide a snippet showing how exactly this can be applied to my case? – d-_-b May 03 '19 at 08:54
  • @d-_-b Your problem might be much simpler. Please see my edit. – ziggystar May 03 '19 at 09:12
  • @ziggystar With this modification, it still compiles. i.e. `init[Int]()` or `init[String]()` throws no compilation errors. Am I missing something? – d-_-b May 03 '19 at 09:50
  • @d-_-b Yes, now it compiles with every type as argument. But given whatever type `A`, you will use only `SomeCaseClass[A]` internally. Isn't this what you want to achieve: `val aaa: SomeTrait[SomeCaseClass[String]] = init[String]()` This means that you cannot write the unwanted code; which is even better than making the unwanted code fail at compilation. – ziggystar May 03 '19 at 10:48
3

This is based on the answer above:

case class SomeCaseClass[M] private (
  value: String
) 

trait SomeTrait[A] {
  def get(oldId: String): SomeCaseClass[A]
}

private[this] def init[A](): SomeTrait[A] = new SomeTrait[A] {
  def get(oldId: String): SomeCaseClass[A] = ???
}

val aaa: SomeTrait[String] = init[String]()

(https://scalafiddle.io/sf/KuXZc0h/3)

This doesn't allow other types than SomeCaseClass to be used with SomeTrait.