0

I get errors by compiling following example code.

abstract class Base
case class A(i: Int)    extends Base
case class B(s: String) extends Base

class Transform {
  def func[T <: Base](arg: T): T = arg match {
    case A(i) => A(i)
    case B(s) => B(s)
  }
}

errors are

Example.scala:9: error: type mismatch;
 found   : A
 required: T
    case A(i) => A(i)
                  ^
Example.scala:10: error: type mismatch;
 found   : B
 required: T
    case B(s) => B(s)
                  ^
two errors found

These errors are reasonable.
To avoid this, I need to put asInstanceOf[T] behind instantiation like A(i).asInstanceOf[T]. However, it is annoying to do like that for all return value if there are a lot of match case patterns.

In addition, I want to use Transform class as parent class and override func() to execute specific operation like below code.

class ExtTransform extends Transform {
  override def func[T <: Base](arg: T): T = arg match {
    case A(i) => A(i + 1)
    case _    => super.func(arg)
  }
}

Are there better ways or some trick?

Mizunashi
  • 313
  • 1
  • 7
  • Possible duplicate of [Pattern matching on generic type in Scala](https://stackoverflow.com/questions/21285235/pattern-matching-on-generic-type-in-scala) – Dmytro Mitin Jun 30 '19 at 03:31
  • Thanks! However, your suggestion is not suited for me. I don't know reflection very well, but `typeTag` seems to be for class type parameter. – Mizunashi Jun 30 '19 at 03:45
  • What's the goal? `arg` is already either a `A` or `B`, which type known so what? – cchantep Jun 30 '19 at 05:30
  • 1
    @cchantep My goal is returning arg's value type value(not variable type value). This operation safety is guranteed by match expression(at least if return value is same type matching type). However, scala compiler cannot detect that. Probably scala compiler ignore type check using match expression to check return type safety now. – Mizunashi Jun 30 '19 at 06:03
  • @cchantep What I want to say is that althought I return the type that is identified by match expression using `arg`, scala compiler yet predicts that `arg`'s type and return type is different (e.g. `arg`'s type is `A` and return type is `B`). – Mizunashi Jun 30 '19 at 06:19

2 Answers2

3

To avoid this, I need to put asInstanceOf[T] behind instantiation like A(i).asInstanceOf[T]. However, it is annoying to do like that for all return value if there are a lot of match case patterns.

Well, that problem is an easy one: put it in one place at the end of the match instead of every branch.

override def func[T <: Base](arg: T): T = (arg match {
  case A(i) => A(i)
  case B(s) => B(s)
}).asInstanceOf[T]

But please note your design is inherently unsafe because there are subtypes of Base other than Base, A, and B: singleton types (a.type), compound types (A with SomeTrait), Null... and any of them can be used as T. It may be better just to have overloads:

class Transform {
  def func(arg: Base): Base = arg match {
    case arg: A => func(arg)
    case arg: B => func(arg)
  }

  def func(arg: A): A = arg
  def func(arg: B): B = arg
}

class ExtTransform extends Transform {
  override def func(arg: A): A = A(arg.i + 1)
}
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
0

I would suggest using a typeclass instead.

sealed trait Base

object Base {
  final case class A() extends Base
  final case class B() extends Base

  sealed trait Builder[T <: Base] {
     def build(): T
  }

  object Builder {
     final implicit val ABuilder: Builder[A] = new Builder[A] {
       override def build(): A = A()
     }

     final implicit val BBuilder: Builder[B] = new Builder[B] {
       override def build(): B = B()
     }
  }
}

object Main extends App {
  def func[T <: Base](implicit builder: Base.Builder[T]): T =
    builder.build()

  func[Base.A] // res: Base.A = A()
  func[Base.B] // res: Base.B = B()
}
  • I'm sorry and my example is inappropriate. As I mentioned in edited version question, I want to use defined class as parent class and customize match expression in child class. Your answer code seems to be impossible to do that. – Mizunashi Jun 30 '19 at 04:12
  • 1
    Sorry for taking too long, I went to sleep right after writing this. I do no see an easy way to combining **typeclasses** with **subtyping overloading**. I think Alexey's answer is the best you can do for now. _(as a side note, Dotty / Scala 3 white macros will allow you to write your original code without problems)_ – Luis Miguel Mejía Suárez Jun 30 '19 at 12:36
  • @LuisMiguelMejíaSuárez `inline def func[T <: Base](arg: T) <: T = inline arg match { case a : A => a; case b : B => b }` still fails for me in Dotty 0.17 with `Found: A(a) Required: T Found: B(b) Required: T`. – Dmytro Mitin Jul 03 '19 at 19:30
  • 1
    Ah, it should be `inline def func[T <: Base](arg: T) <: Any = inline arg match { case a : A => a; case b : B => b } }` `val a: A = func(A(1))` `val b: B = func(B("a"))`. – Dmytro Mitin Jul 03 '19 at 19:35