I'm writing a math library for complex vector computing, part of which looks like this:
object Example1 {
val two = 2
val three = 3
// SU means 'special unitary group'
trait SU_n[D <: Int] {
def plus(v1: ComplexVector[D], v2: ComplexVector[D])(
implicit ev: D =:= two.type
): ComplexVector[D] = {
//TODO: some unspeakable magic here
???
}
}
class ComplexVector[D <: Int: ClassTag](xyzw: List[(Double, Double)]) {
{
assert(xyzw.size.isInstanceOf[D])
}
}
type Quaternion = ComplexVector[two.type]
val alsoTwo = 2
object SU_2 extends SU_n[two.type] {}
object SU_Also2 extends SU_n[alsoTwo.type] {}
object SU_3 extends SU_n[three.type] {}
val q = new Quaternion(List(1.0 -> 2.0, 3.0 -> 4.0))
{
val v2 = SU_2.plus(q, q)
val also_v2 = SU_Also2.plus(q, q)
}
val vec =
new ComplexVector[three.type](List(1.0 -> 2.0, 3.0 -> 4.0, 5.0 -> 6.0))
// This will break
// {
// val v3 = SU_3.plus(vec, vec)
// }
}
the infix type =:=
is used to ensure that function plus
won't be usable on vectors other than quaternions, when I compile it, I got the following error message:
Error: type mismatch;
found : <mypackage>.Example1.two.type (with underlying type Int)
required: AnyRef
type Quaternion = ComplexVector[two.type]
The strange thing is that I can't find anywhere in the implementation of infix class =:=
that mandates its operand to be AnyVal
, so why I'm getting this error? And how to fix/bypass it to achieve the requirement? (namely, create a function that can only be applied on quaternion)
Singleton types produced by .type
don't work in quite the way you're imagining - in particular, they're not literal types. To see this, verify:
val two = 2
val alsoTwo = 2
type V = two.type
type W = alsoTwo.type
implicitly[V =:= W] // will fail with an implicit error
val test : V = 2 // will fail with "expression does not conform to type"
def id(x: V) : W = x //will fail with "expression does not conform to type"
Historically, this .type
syntax was only meant to be used for AnyRef
, with the compiler not exposing singleton types for primitive types. As of 2.13, this has changed in that the language now supports literal types, but it looks like the behaviour of .type
is still the same.
If 2.13 is an option, you can just write
trait SU_n[D <: Int] {
def plus(v1: ComplexVector[D], v2: ComplexVector[D])(
implicit ev: D =:= 2
): ComplexVector[D] = {
//TODO: some unspeakable magic here
???
}
}
type Quaternion = ComplexVector[2]
object SU_2 extends SU_n[2] {}
object SU_Also2 extends SU_n[2] {}
object SU_3 extends SU_n[3] {}
and everything will work as intended. If you need to stick with 2.11 or 2.12, I suggest looking at shapeless if this is the route you want to go down.
That said, as per my comment, this seems like a very odd way of solving the problem, since the idea of having a trait T[A] { def foo(a: A) }
where calling foo
will only compile if A
is a specific type strikes me as fairly pathological. If you really want a plus
method only accessible to the quaternions, you could do something like
implicit class QuaternionSupport(quaternions: SU_n[2]) {
def plus(quaternion1: Quaternion, quaternion2: Quaternion) : Quaternion = ???
}
and the method won't be present for other dimensions.