Well after randomly bashing the keyboard a lot and reading as much as I could understand about type constraints, this is what I have come up with:
// A without B is C
sealed abstract class isWithout[A, B, C]
object Syntax {
implicit def composedWithout[A <: C, B, C](implicit ev: C <:!< B): isWithout[A, B, C] = new isWithout[A, B, C] {
def apply(a: A) = a
}
type without[A, B] = {
type l[C] = isWithout[A, B, C]
}
}
Test that it seems to work:
implicitly[isWithout[POSCONST, POSITIVE, CONST]]
implicitly[isWithout[POSINTCONST, DISCRETE, POSNUMCONST]]
implicitly[isWithout[POSINTCONST, DISCRETE, POSITIVE]]
implicitly[isWithout[POSNUM, CONST, POSNUM]]
implicitly[isWithout[POSCONST, CONST, POSITIVE ]]
implicitly[isWithout[POSCONST, POSITIVE, CONST ]]
implicitly[isWithout[INTEGER, DISCRETE, NUMERIC ]]
implicitly[isWithout[POSINTCONST, CONST, POSINT ]]
And fails when it should:
implicitly[isWithout[POSINTCONST, INTCONST, POSINTCONST]]
implicitly[isWithout[NUMERIC, ANYSYNTAX, ANYSYNTAX]]
implicitly[isWithout[INTEGER, POSITIVE, POSINT]]
implicitly[isWithout[POSNUM, DISCRETE, POSCONST]]
implicitly
gets the compiler to look for an implicit function in the current implicit scope that can produce an object of the required type (in this case, an instance of the class isWithout). If it finds one that satisfies the type signature then it compiles (I don't think it matters what the apply
method defined in the class returns). The important point is the type signature, which makes use of <:!<
mentioned in the question and borrowed from another SO answer by Miles.
This type signature says: A is a subtype of C and C must not be a subtype of B. An alternative version (corresponding to the first definition in the question) would be:
implicit def composedWithout[A <: C with B, B, C](implicit ev: C <:!< B): isWithout[A, B, C] = new isWithout[A, B, C] {
def apply(a: A, b: B) = a
}
This can be used in a few ways:
To check the validity of the type hierarchy (ie. test cases), as shown with the above use of
implicitly
.To specify the type of a method parameter:
def test[R : without[POSINTCONST, DISCRETE]#l](v: R) = v
Note that this uses the type projection
without#l[C] = isWithout[A, B, C]
as a context bound which the compiler desugars to:def test[R](v: R)(implicit $ev0: isWithout[POSINTCONST, DISCRETE, R]) = v
Thus it requires the specified implicit to be in scope.
As a type constraint, as requested in the original question:
case class Subtract[A <: R, R <: A without POSITIVE]( arg1: Expression[A], arg2: Expression[A] ) extends BinaryPrimitive[A, R]( arg1, arg2 )
This compiles, although I admit I haven't run anything yet so it might not be doing what I think it's doing.