Skip to content

Instantly share code, notes, and snippets.

@manjuraj
Last active September 15, 2017 19:43
Show Gist options
  • Save manjuraj/b56e3377d562fdf0d283 to your computer and use it in GitHub Desktop.
Save manjuraj/b56e3377d562fdf0d283 to your computer and use it in GitHub Desktop.
generalized type constraints: =:=, <:< and <%<
//
// References
// - http://stackoverflow.com/questions/3427345/what-do-and-mean-in-scala-2-8-and-where-are-they-documented
// - http://stackoverflow.com/questions/2603003/operator-in-scala
// - https://gist.github.com/retronym/229163
// - http://debasishg.blogspot.com/2010/08/using-generalized-type-constraints-how.html#sthash.GKfUGq9p.dpuf
// - http://www.scala-lang.org/old/node/4041.html
// - https://groups.google.com/forum/#!searchin/scala-user/generic$20type$20constraint/scala-user/mgx9-TUapQo/tiN6x818dKsJ
//
// Scala 2.8 introduces generalized type constraints which 3 variants:
//
// - A =:= B means A must be exactly B
// (equalTo)
// - A <:< B means A must be a subtype of B, i.e. A must conform to B
// (analogous to the simple type constraint <:)
// (conformsTo)
// - A <%< B means A must be viewable as B, possibly via implicit conversion
// (analogous to the simple type constraint <%)
// (visibleAs)
//
// Unlike <: or >:, the generalized type constraints are not operators. They
// are classes, and instances of which are implicitly provided by the compiler
// itself to enforce conformance to the type constraints.
//
//
// Type Constraints from Predef.scala:
// used, for example, in the encoding of generalized constraints
// we need a new type constructor `<:<` and evidence `conforms`, as
// reusing `Function2` and `identity` leads to ambiguities (any2stringadd is inferred)
// to constrain any abstract type T that's in scope in a method's argument list (not just the method's own type parameters)
// simply add an implicit argument of type `T <:< U`, where U is the required upper bound (for lower-bounds, use: `U <: T`)
// in part contributed by Jason Zaugg
//
sealed abstract class <:<[-From, +To] extends (From => To)
implicit def conforms[A]: A <:< A = new (A <:< A) {
def apply(x: A) = x
}
// not in the <:< companion object because it is also intended to subsume identity (which is no longer implicit)
sealed abstract class =:=[From, To] extends (From => To)
object =:= {
implicit def tpEquals[A]: A =:= A = new (A =:= A) {
def apply(x: A) = x
}
}
sealed abstract class <%<[-From, +To] extends (From => To)
object <%< {
implicit def conformsOrViewsAs[A <% B, B]: A <%< B = new (A <%< B) {
def apply(x: A) = x
}
}
//
// Examples
//
case class Foo[A](a: A) { // 'A' can be substituted with any type
// getStringLength can only be used if this is a Foo[String]
def length(implicit evidence: A =:= String) = a.length
}
scala> Foo(1234).length
<console>:10: error: Cannot prove that Int =:= String.
Foo(1234).length
^
scala> Foo("asdf").length
res1: Int = 4
//
// orNull from Option.scala
//
/**
* Returns the option's value if it is nonempty,
* or `null` if it is empty.
* Although the use of null is discouraged, code written to use
* $option must often interface with code that expects and returns nulls.
* @example {{{
* val initalText: Option[String] = getInitialText
* val textField = new JComponent(initalText.orNull,20)
* }}}
*/
@inline final def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse null
scala> Some("string").orNull
res3: String = string
scala> Some(1231).orNull
<console>:8: error: Cannot prove that Null <:< Int.
Some(1231).orNull
//
// joinsRight and joinsLeft from Either.scala
//
/**
* Joins an `Either` through `Right`.
*
* This method requires that the right side of this Either is itself an
* Either type. That is, this must be some type like: {{{
* Either[A, Either[A, C]]
* }}} (which respects the type parameter bounds, shown below.)
*
* If this instance is a Right[Either[A, C]] then the contained Either[A, C]
* will be returned, otherwise this value will be returned unmodified.
*
* @example {{{
* Right[String, Either[String, Int]](Right(12)).joinRight // Result: Right(12)
* Right[String, Either[String, Int]](Left("flower")).joinRight // Result: Left("flower")
* Left[String, Either[String, Int]]("flower").joinRight // Result: Left("flower")
* }}}
*
* This method, and `joinLeft`, are analogous to `Option#flatten`
*/
def joinRight[A1 >: A, B1 >: B, C](implicit ev: B1 <:< Either[A1, C]): Either[A1, C] = this match {
case Left(a) => Left(a)
case Right(b) => b
}
/**
* Joins an `Either` through `Left`.
*
* This method requires that the left side of this Either is itself an
* Either type. That is, this must be some type like: {{{
* Either[Either[C, B], B]
* }}} (which respects the type parameter bounds, shown below.)
*
* If this instance is a Left[Either[C, B]] then the contained Either[C, B]
* will be returned, otherwise this value will be returned unmodified.
*
* {{{
* Left[Either[Int, String], String](Right("flower")).joinLeft // Result: Right("flower")
* Left[Either[Int, String], String](Left(12)).joinLeft // Result: Left(12)
* Right[Either[Int, String], String]("daisy").joinLeft // Result: Right("daisy")
* }}}
*
* This method, and `joinRight`, are analogous to `Option#flatten`
*/
def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match {
case Left(a) => a
case Right(b) => Right(b)
}
//
// toMap from TraversableOnce
// - toMap only works if Traversable contains 2-tuples
//
def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U] = {
val b = immutable.Map.newBuilder[T, U]
for (x <- self)
b += x
b.result
}
//
// Example that illustrates how type constraints can be used to supply
// method to a generic class that can only be used under certain
// conditions
//
case class Pair[T](first: T, second: T) {
def smaller(implicit ev: T <:< Ordered[T]): T = {
if (first < second) first else second
}
}
//
// Use type constraint when the type inteference cannot figure out the
// types. Here the type inferencer attempts to determinte the types
// A and C in a single step. Help it along, by first matching on type
// C and then on A
//
def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.tail)
scala> firstLast(List(1,2,3))
<console>:9: error: inferred type arguments [Nothing,List[Int]] do not conform to method firstLast's type parameter bounds [A,C <: Iterable[A]]
firstLast(List(1,2,3))
^
<console>:9: error: type mismatch;
found : List[Int]
required: C
firstLast(List(1,2,3))
^
def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) = (it.head, it.tail)
scala> firstLast(List(1,2,3))
res2: (Int, Iterable[Int]) = (1,List(2, 3))
// Alternatively, you can get specific about types by using
// higher kinded types
scala> import scala.language.higherKinds
scala> def firstLast[A, C[B] <: Iterable[B]](it: C[A]) = (it.head, it.tail)
firstLast: [A, C[B] <: Iterable[B]](it: C[A])(A, Iterable[A])
scala> firstLast(List(1,2,3))
res1: (Int, Iterable[Int]) = (1,List(2, 3))
// This however doesn't give what I intended
scala> def firstLast[T, C[_] <: Iterable[_]](it: C[T]) = (it.head, it.tail)
firstLast: [T, C[_] <: Iterable[_]](it: C[T])(Any, Repr)
scala> firstLast(List(1,2,3))
res0: (Any, Repr) = (1,List(2, 3))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment