Reputation: 13716
Fiddling with sample code demonstrating type bounds, I changed the original code below from using a case class to a plain class, for the definition of class MyInt
, the only class in this snippet.
trait Similar {
def isSimilar(x: Any): Boolean
}
class MyInt(x: Int) extends Similar {
def isSimilar(m: Any): Boolean =
m.isInstanceOf[MyInt] &&
m.asInstanceOf[MyInt].x == x
}
object UpperBoundTest extends App {
def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean =
if (xs.isEmpty) false
else if (e.isSimilar(xs.head)) true
else findSimilar[T](e, xs.tail)
val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3))
println(findSimilar[MyInt](MyInt(4), list))
println(findSimilar[MyInt](MyInt(2), list))
}
That no longer compiles
[error] 7: type mismatch;
[error] found : MyInt
[error] required: ?{def x: ?}
[error] Note that implicit conversions are not applicable because they are ambiguous:
[error] both method any2Ensuring in object Predef of type [A](x: A)Ensuring[A]
[error] and method any2ArrowAssoc in object Predef of type [A](x: A)ArrowAssoc[A]
[error] are possible conversion functions from MyInt to ?{def x: ?}
[error] m.asInstanceOf[MyInt].x == x
[error] ^
This is an interesting case to me, as sometimes I find myself refactoring case classes to plain classes while refactoring inheritance, and clearly some changes need to made in the code in order to enjoy a smooth transition that preserves the code working.
Switching .AsInstanceOf
to an equivalent match (m match {case m:MyInt => m.x == x; case _ => false}
) yields the same compilation error. Using a more naive match does not compile either:
trait Similar {
def isSimilar(x: Any): Boolean
}
class MyInt(x: Int) extends Similar {
def isSimilar(m: Any): Boolean = {
m match {case m:MyInt => true; case _ => false}
}
}
object UpperBoundTest extends App {
val a = new MyInt(4)
def findSimilar[T <: Similar](e: T, xs: List[T]): Boolean =
if (xs.isEmpty) false
else if (e.isSimilar(xs.head)) true
else findSimilar[T](e, xs.tail)
val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3))
println(findSimilar[MyInt](MyInt(4), list))
println(findSimilar[MyInt](MyInt(2), list))
}
18: not found: value MyInt
[error] val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3))
[error] ^
18: not found: value MyInt
[error] val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3))
[error] ^
18: not found: value MyInt
[error] val list: List[MyInt] = List(MyInt(1), MyInt(2), MyInt(3))
[error] ^
19: not found: value MyInt
[error] println(findSimilar[MyInt](MyInt(4), list))
[error] ^
20: not found: value MyInt
[error] println(findSimilar[MyInt](MyInt(2), list))
[error] ^
[error] 5 errors found
Are non-case classes a practice to be enitrely discouraged other than using them for inheriting them, or when you will never need to check their type? when would you ever use a non-case class?
Upvotes: 1
Views: 293
Reputation: 170815
Complete code you need to avoid compilation failure in any case, including pattern matching:
class MyInt(val x: Int) extends Similar
object MyInt {
def apply(x: Int) = new MyInt(x)
// if more than one field, return Some(tuple of all fields)
def unapply(myInt: MyInt): Option[Int] = Some(myInt.x)
}
If you also want behavior to be the same as original, you need to define equals
and hashCode
methods as well:
class MyInt(val x: Int) extends Similar {
def equals(y: Any) = y match {
case MyInt(z) => x == z
case _ => false
}
def hashCode = x
}
Upvotes: 2
Reputation: 108151
Simply declare your class as
class MyInt(val x: Int) extends Similar
For case classes val
is the default, whereas on regular classes you need to explicitly add it to signal that you want to synthesize the accessors for the constructor parameters
Also, in case classes a companion object providing a default apply method is automatically synthesized, allowing you to call
MyInt(2)
as opposed to
new MyInt(2)
You either have to use the latter, or manually provide an apply method in a companion object.
Bottom line, if you need to match on a class, case classes are much more convenient, as you can skip the downcast (which is implicitly performed when you match on the type doing x: MyInt
)
Classes are not discouraged, simply case-classes are much more convenient in your specific use -case
Upvotes: 5