Generalized Type Constraints, also known as
<%<(deprecated though) and
=:=, also known as type relation operator, or call whatever you want, are not operators but identifiers. It’s quite confusing for new comers to distinguish them from operators, well…, identifiers which are not that esoteric.
This is just plain Scala feature that non-alphanum symbols can act as legal identifiers, just like
More specifically, they are type-constructors. But before we inspect their implementations, let’s first consider their usage.
You want to implement a generic container for every type, however, you also want to add a special method that only applies to
Special type. (notice: this is different from the annotation
@specialized which deals with JVM’s primitive type. Here
Special is just a plain old scala type)
Why? The type bound
A <: Int does not work.
A has been defined at the class declaration, in the class body Scala compiler requries every type bound is consistent with A’s definition. Here,
A has no bound so it is bounded by
Instead of setting type bound, methods may ask for some kinds of specific ad-hoc “evidence” for a type.
evidence is an implicit provided by scala predef. And
A =:= Int is just a type like
Map[Int, String], but is infixed due to scala’s syntactic sugar.
Scala does not impose type constraints until the specific method is called, so
addIt does not violate
A‘s definition. Still, given the implicit
evidence, compiler can still infer that value in
addIt is an sub-instance of
As stated before, type constraints are ad-hoc. So it can achieve type inference more specific than type bound. (Fairly, this is the power of implicit).
1 is clearly
Int but why does compiler infer it as
B <: A bound requires the first argument type is a super type of the second.
A is inferred as the most general type between
<:< comes to help.
Because generalized type constraints does not interfere with inference,
Int here. Only then does the compiler find evidence for
<:<[Int, List[Int]] and then fails.
(Actually, implicit can feedback type information back to inference, see typelevel programming’s
HList and scala collection library’s
Also implicit conversion does not impact
=:= is just a type constructor in scala.
It is somewhat like
Map[A, B], that is,
=:= is defined like
so in the implictly’s bracket,
Int =:= Int is just a type
A =:= B is the infix form of type parameterization for
non-alphanumeric identifier. It is equivalent to
so one can define implicts for
=:=, so that compiler can find
implictly[A =:= B] is compiled,
compiler tries to find the correct implicit evidence.
If and only If A and B are the same, say Int, the compiler can find
=:=[Int, Int], by the result of
implicit function EqualTypeEvidence[Int]
More compelling is <:<, the conformance evidence,
it leverages variance annotation in scala
String <:< java.io.Serializable is needed,
compiler tries to find an instance of
It can only find instance of the type
(or another alternative
But given the variance annotation of <:<,
since String is the very type String
and String is a subtype of Serializable and B is in a covariant position
, or, in another direction
snice Serializable is a supertype of String and A is in a contravariant position
and Serializable is the very type Serializable
<:<[String, String] is a subtype of
So compiler finds the correct implicit instance as the evidence that
String is a subtype of Serializable. By the principle of subtype subsititution.
Similarly we can define
The actual implementations uses singleton pattern so it is more efficient. For this illustration post, sloppy implementation is just fine :).