I have a class with a constrained type parameter.
I've tried with identity
but the return type is not precise.
And in the method identityTP
, I need to specify again the constraint
How to avoid to duplicate this constraint with methods that work on this type ?
Here is an example :
sealed trait Location
case object Single extends Location
case object Multi extends Location
final case class Log[L <: Location](l: L)
def identity(log: Log[_]): Log[_] = log
def identityTP[L<: Location](log: Log[L]): Log[L] = log
Actually this is not a duplication. L
in
final case class Log[L <: Location](l: L)
and L
in
def identityTP[L <: Location](log: Log[L]): Log[L] = log
are two completely different type parameters. This could be more clear if you used different identifiers
final case class Log[L <: Location](l: L)
def identityTP[L1 <: Location](log: Log[L1]): Log[L1] = log
An upper bound for one type parameter doesn't duplicate an upper bound for other type parameter.
Also this is not a duplication because actually upper bounds can be different
sealed trait SubLocation extends Location
final case class Log[L <: Location](l: L)
def identityTP[L1 <: SubLocation](log: Log[L1]): Log[L1] = log
If you don't want to create the second type parameter you can make identityTP
nested into Log
(making it Log
's method)
final case class Log[L <: Location](l: L) {
def identityTP: Log[L] = this
}
Sometimes it can help if you make L
a type member rather than type parameter
trait Log {
type L <: Location
val l: L
}
object Log {
// def apply[_L <: Location](_l: _L): Log { type L = _L} = new Log {
// override type L = _L
// override val l: L = _l
// }
def apply[_L <: Location](_l: _L): Log = new Log {
override type L = _L
override val l: L = _l
}
}
// def identityTP(log: Log): Log = log
def identityTP(log: Log): Log { type L = log.L } = log
Notice that although we have to repeat upper bound in apply
but we don't have in identityTP
.
Normally it's not a big deal to repeat upper bound when necessary
class MyClass[A <: A1]
def foo[A <: A1](mc: MyClass[A]) = ???
def bar[A <: A1](mc: MyClass[A]) = ???
When this becomes cumbersome
class MyClass[A <: A1, B <: B1, C <: C1]
def foo[A <: A1, B <: B1, C <: C1](mc: MyClass[A, B, C]) = ???
def bar[A <: A1, B <: B1, C <: C1](mc: MyClass[A, B, C]) = ???
you should redesign your abstractions. For example
trait Tuple {
type A <: A1
type B <: B1
type C <: C1
}
class MyClass[T <: Tuple]
def foo[T <: Tuple](mc: MyClass[T]) = {
//T#A, T#B, T#C instead of A, B, C
???
}
or
class MyClass[T <: Tuple](val t: T)
//class MyClass(val t: Tuple)
def foo[T <: Tuple](mc: MyClass[T]) = {
//def foo(mc: MyClass) = {
import mc.t
//t.A, t.B, t.C instead of A, B, C
???
}
Also sometimes you can play with replacing type bounds with type constraints
final case class Log[L](l: L)(implicit ev: L <:< Location)
def identityTP[L](log: Log[L])(implicit ev: L <:< Location): Log[L] = log
Although this doesn't remove repetitions but there are ways to fight against repetitions among implicit parameters as well (type classes). See How to wrap a method having implicits with another method in Scala?