Last active
September 15, 2017 19:43
-
-
Save manjuraj/b56e3377d562fdf0d283 to your computer and use it in GitHub Desktop.
generalized type constraints: =:=, <:< and <%<
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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