I had to write a context bound for Ordering[Option[T]]
it turns out that the solution was
def test[T: ({type L[x] = Ordering[Option[x]]})#L](value1: Option[T], value2: Option[T]) = {
val e = implicitly(Ordering[Option[T]].compare(value1, value2))
}
see How to define a context bound with a higher kinded Type (Type Constructor)
So played with type lambda a little to understand better, leading me to write the version without type lambda:
type L[x] = Ordering[Option[x]]
def testN[T: L](value1: Option[T], value2: Option[T]) = {
implicitly[L[T]].compare(value1, value2)
}
Many example of the use of Type lambda are for type constructor with 2 parameters such MAP[K,V].
In this case we do not have that problem.
So i just wonder, why not having something like this
def test[T: Ordering[Option]](value1: Option[T], value2: Option[T]) = {
val e = implicitly(Ordering[Option[T]].compare(value1, value2))
}
obviously it does not work. I think I understood the all point, there is no type constructor Ordering[Option[_]]
defined.
What we have in ordering is:
trait OptionOrdering[T] extends Ordering[Option[T]] {
def optionOrdering: Ordering[T]
def compare(x: Option[T], y: Option[T]) = (x, y) match {
case (None, None) => 0
case (None, _) => -1
case (_, None) => 1
case (Some(x), Some(y)) => optionOrdering.compare(x, y)
}
}
implicit def Option[T](implicit ord: Ordering[T]): Ordering[Option[T]] =
new OptionOrdering[T] { val optionOrdering = ord }
As it stands Ordering[Option[T]]
in the definition above, is Ordering[Option[T]] forSome {type T}
akka Existential, therefore a proper type, and not a type constructor.
So if i am correct what we do here:
({type L[x] = Ordering[Option[x]]})#L
or here:
type L[x] = Ordering[Option[x]]
is defining a Type Constructor Ordering[Option[_]]
.
Question 1:
1 - is my understanding correct ? is that what the Type Lambda does here ?
2 - I'm a bit confused here, so type alias is something that allows you to create type constructor out of the composition of other type constructors. In a sense i am trying to understand the formal role of type alias with type variable.
scala> type e0 = Ordering[Option[_]]
defined type alias e0
scala> :kind -v e0
e0's kind is A
*
This is a proper type.
scala> type e1[w] = Ordering[Option[w]]
defined type alias e1
scala> :kind -v e1
e1's kind is F[A]
* -> *
This is a type constructor: a 1st-order-kinded type.
scala>
Anonymous type constructor
({type L[x] = Ordering[Option[x]]})#L
is to named type constructor
type L[x] = Ordering[Option[x]]
what anonymous (value) constructor
(x: Int) => x + 1
is to named (value) constructor
val f = (x: Int) => x + 1
for example
scala> lazy val v: (({type L[x] = Ordering[Option[x]]})#L)[Int] = ???
lazy val v: scala.math.Ordering[Option[Int]] // unevaluated
scala> lazy val v: L[Int] = ???
lazy val v: L[Int] // unevaluated
scala> lazy val v = ((x: Int) => x + 1)(41)
lazy val v: Int // unevaluated
scala> lazy val v = f(41)
lazy val v: Int // unevaluated
In Scala 3 (Dotty) you will be able to replace the "atrocity"
There's less need for kind projector in dotty anyway because we have native type lambdas instead of the atrocity involving structural types.
with beautiful type lambda syntax
Starting dotty REPL...
scala> lazy val v: (({type L[x] = Ordering[Option[x]]})#L)[Int] = ???
lazy val v: Ordering[Option[Int]]
scala> lazy val v: ([x] =>> Ordering[Option[x]])[Int] = ???
lazy val v: Ordering[Option[Int]]
The context bound syntax
def test[T: ({type L[x] = Ordering[Option[x]]})#L](value1: Option[T], value2: Option[T]) = ???
is equivalent to
def test[T](value1: Option[T], value2: Option[T])(implicit ev: (({type L[x] = Ordering[Option[x]]})#L)[T]) = ???
which simplifies to
def test[T](value1: Option[T], value2: Option[T])(implicit ev: Ordering[Option[T]]) = ???