diff --git a/doc/changelog/0.11.1.md b/doc/changelog/0.11.1.md index 051920f57..fbe0fe9ea 100644 --- a/doc/changelog/0.11.1.md +++ b/doc/changelog/0.11.1.md @@ -1,5 +1,6 @@ ## 0.11.1 +* `extra.ReusableFn` is now contravariant in inputs and covariant in outputs like normal functions. * Upgrade Monocle to 1.2.1, for official Scala.JS support. * Upgrade Scalaz 7.2.{1 ⇒ 2}. diff --git a/extra/src/main/scala/japgolly/scalajs/react/extra/ReusableFn.scala b/extra/src/main/scala/japgolly/scalajs/react/extra/ReusableFn.scala index 85f018987..f5bafc9e5 100644 --- a/extra/src/main/scala/japgolly/scalajs/react/extra/ReusableFn.scala +++ b/extra/src/main/scala/japgolly/scalajs/react/extra/ReusableFn.scala @@ -13,13 +13,13 @@ import japgolly.scalajs.react._ * * @since 0.9.0 */ -sealed abstract class ReusableFn[A, B] extends AbstractFunction1[A, B] { - private[extra] def reusable: PartialFunction[ReusableFn[A, B], Boolean] +sealed abstract class ReusableFn[-A, +B] extends AbstractFunction1[A, B] { + private[extra] def reusable[AA <: A, BB >: B]: PartialFunction[AA ~=> BB, Boolean] - def asVar(value: A)(implicit r: Reusability[A], ev: ReusableFn[A, B] =:= ReusableFn[A, Callback]): ReusableVar[A] = + def asVar[AA <: A](value: AA)(implicit r: Reusability[AA], ev: (A ~=> B) <:< (AA ~=> Callback)): ReusableVar[AA] = new ReusableVar(value, ev(this))(r) - def asVarR(value: A, r: Reusability[A])(implicit ev: ReusableFn[A, B] =:= ReusableFn[A, Callback]): ReusableVar[A] = + def asVarR[AA <: A](value: AA, r: Reusability[AA])(implicit ev: (A ~=> B) <:< (AA ~=> Callback)): ReusableVar[AA] = asVar(value)(r, ev) def dimap[C, D](f: (A => B) => C => D): C ~=> D = @@ -31,7 +31,7 @@ sealed abstract class ReusableFn[A, B] extends AbstractFunction1[A, B] { def contramap[C](f: C => A): C ~=> B = dimap(f.andThen) - def fnA(a: A)(implicit ra: Reusability[A]): ReusableFnA[A, B] = + def fnA[AA <: A, BB >: B](a: AA)(implicit ra: Reusability[AA]): ReusableFnA[AA, BB] = new ReusableFnA(a, this) } @@ -98,44 +98,44 @@ object ReusableFn { // =================================================================================================================== private type R[A] = Reusability[A] - private class Fn1[Y, Z](val f: Y => Z) extends ReusableFn[Y, Z] { + private class Fn1[-Y, +Z](val f: Y => Z) extends ReusableFn[Y, Z] { override def apply(a: Y) = f(a) - override private[extra] def reusable = { case x: Fn1[Y, Z] => f eq x.f } + override private[extra] def reusable[I <: Y, O >: Z] = { case x: Fn1[I, O] => f eq x.f } } - private class Fn2[A: R, Y, Z](val f: (A, Y) => Z) extends ReusableFn[A, Y ~=> Z] { + private class Fn2[A: R, -Y, +Z](val f: (A, Y) => Z) extends ReusableFn[A, Y ~=> Z] { override def apply(a: A) = new Cur1(a, f) - override private[extra] def reusable = { case x: Fn2[A, Y, Z] => f eq x.f } + override private[extra] def reusable[I <: A, O >: (Y ~=> Z)] = { case x: Fn2[I, _, _] => f eq x.f } } - private class Fn3[A: R, B: R, Y, Z](val f: (A, B, Y) => Z) extends ReusableFn[A, B ~=> (Y ~=> Z)] { + private class Fn3[A: R, B: R, -Y, +Z](val f: (A, B, Y) => Z) extends ReusableFn[A, B ~=> (Y ~=> Z)] { private val c2 = cur2(f) override def apply(a: A) = new Cur1(a, c2) - override private[extra] def reusable = { case x: Fn3[A, B, Y, Z] => f eq x.f } + override private[extra] def reusable[I <: A, O >: (B ~=> (Y ~=> Z))] = { case x: Fn3[I, _, _, _] => f eq x.f } } - private class Fn4[A: R, B: R, C: R, Y, Z](val f: (A, B, C, Y) => Z) extends ReusableFn[A, B ~=> (C ~=> (Y ~=> Z))] { + private class Fn4[A: R, B: R, C: R, -Y, +Z](val f: (A, B, C, Y) => Z) extends ReusableFn[A, B ~=> (C ~=> (Y ~=> Z))] { private val c3 = cur3(f) private val c2 = cur2(c3) override def apply(a: A) = new Cur1(a, c2) - override private[extra] def reusable = { case x: Fn4[A, B, C, Y, Z] => f eq x.f } + override private[extra] def reusable[I <: A, O >: (B ~=> (C ~=> (Y ~=> Z)))] = { case x: Fn4[I, _, _, _, _] => f eq x.f } } - private class Fn5[A: R, B: R, C: R, D: R, Y, Z](val f: (A, B, C, D, Y) => Z) extends ReusableFn[A, B ~=> (C ~=> (D ~=> (Y ~=> Z)))] { + private class Fn5[A: R, B: R, C: R, D: R, -Y, +Z](val f: (A, B, C, D, Y) => Z) extends ReusableFn[A, B ~=> (C ~=> (D ~=> (Y ~=> Z)))] { private val c4 = cur4(f) private val c3 = cur3(c4) private val c2 = cur2(c3) override def apply(a: A) = new Cur1(a, c2) - override private[extra] def reusable = { case x: Fn5[A, B, C, D, Y, Z] => f eq x.f } + override private[extra] def reusable[I <: A, O >: (B ~=> (C ~=> (D ~=> (Y ~=> Z))))] = { case x: Fn5[I, _, _, _, _, _] => f eq x.f } } - private class Fn6[A: R, B: R, C: R, D: R, E: R, Y, Z](val f: (A, B, C, D, E, Y) => Z) extends ReusableFn[A, B ~=> (C ~=> (D ~=> (E ~=> (Y ~=> Z))))] { + private class Fn6[A: R, B: R, C: R, D: R, E: R, -Y, +Z](val f: (A, B, C, D, E, Y) => Z) extends ReusableFn[A, B ~=> (C ~=> (D ~=> (E ~=> (Y ~=> Z))))] { private val c5 = cur5(f) private val c4 = cur4(c5) private val c3 = cur3(c4) private val c2 = cur2(c3) override def apply(a: A) = new Cur1(a, c2) - override private[extra] def reusable = { case x: Fn6[A, B, C, D, E, Y, Z] => f eq x.f } + override private[extra] def reusable[I <: A, O >: (B ~=> (C ~=> (D ~=> (E ~=> (Y ~=> Z)))))] = { case x: Fn6[I, _, _, _, _, _, _] => f eq x.f } } @inline private def cur2[A:R, B:R, Y, Z](f: (A,B, Y) => Z): (A,B ) => (Y ~=> Z) = new Cur2(_,_, f) @@ -143,29 +143,29 @@ object ReusableFn { @inline private def cur4[A:R, B:R, C:R, D:R, Y, Z](f: (A,B,C,D, Y) => Z): (A,B,C,D ) => (Y ~=> Z) = new Cur4(_,_,_,_, f) @inline private def cur5[A:R, B:R, C:R, D:R, E:R, Y, Z](f: (A,B,C,D,E,Y) => Z): (A,B,C,D,E) => (Y ~=> Z) = new Cur5(_,_,_,_,_,f) - private class Cur1[A: R, Y, Z](val a: A, val f: (A, Y) => Z) extends ReusableFn[Y, Z] { + private class Cur1[A: R, -Y, +Z](val a: A, val f: (A, Y) => Z) extends ReusableFn[Y, Z] { override def apply(y: Y): Z = f(a, y) - override private[extra] def reusable = { case x: Cur1[A, Y, Z] => (f eq x.f) && (a ~=~ x.a) } + override private[extra] def reusable[I <: Y, O >: Z] = { case x: Cur1[A, _, _] => (f eq x.f) && (a ~=~ x.a) } } - private class Cur2[A: R, B: R, Y, Z](val a: A, val b: B, val f: (A, B, Y) => Z) extends ReusableFn[Y, Z] { + private class Cur2[A: R, B: R, -Y, +Z](val a: A, val b: B, val f: (A, B, Y) => Z) extends ReusableFn[Y, Z] { override def apply(y: Y): Z = f(a, b, y) - override private[extra] def reusable = { case x: Cur2[A, B, Y, Z] => (f eq x.f) && (a ~=~ x.a) && (b ~=~ x.b) } + override private[extra] def reusable[I <: Y, O >: Z] = { case x: Cur2[A, B, _, _] => (f eq x.f) && (a ~=~ x.a) && (b ~=~ x.b) } } - private class Cur3[A: R, B: R, C: R, Y, Z](val a: A, val b: B, val c: C, val f: (A, B, C, Y) => Z) extends ReusableFn[Y, Z] { + private class Cur3[A: R, B: R, C: R, -Y, +Z](val a: A, val b: B, val c: C, val f: (A, B, C, Y) => Z) extends ReusableFn[Y, Z] { override def apply(y: Y): Z = f(a, b, c, y) - override private[extra] def reusable = { case x: Cur3[A, B, C, Y, Z] => (f eq x.f) && (a ~=~ x.a) && (b ~=~ x.b) && (c ~=~ x.c) } + override private[extra] def reusable[I <: Y, O >: Z] = { case x: Cur3[A, B, C, _, _] => (f eq x.f) && (a ~=~ x.a) && (b ~=~ x.b) && (c ~=~ x.c) } } - private class Cur4[A: R, B: R, C: R, D: R, Y, Z](val a: A, val b: B, val c: C, val d: D, val f: (A, B, C, D, Y) => Z) extends ReusableFn[Y, Z] { + private class Cur4[A: R, B: R, C: R, D: R, -Y, +Z](val a: A, val b: B, val c: C, val d: D, val f: (A, B, C, D, Y) => Z) extends ReusableFn[Y, Z] { override def apply(y: Y): Z = f(a, b, c, d, y) - override private[extra] def reusable = { case x: Cur4[A, B, C, D, Y, Z] => (f eq x.f) && (a ~=~ x.a) && (b ~=~ x.b) && (c ~=~ x.c) && (d ~=~ x.d) } + override private[extra] def reusable[I <: Y, O >: Z] = { case x: Cur4[A, B, C, D, _, _] => (f eq x.f) && (a ~=~ x.a) && (b ~=~ x.b) && (c ~=~ x.c) && (d ~=~ x.d) } } - private class Cur5[A: R, B: R, C: R, D: R, E: R, Y, Z](val a: A, val b: B, val c: C, val d: D, val e: E, val f: (A, B, C, D, E, Y) => Z) extends ReusableFn[Y, Z] { + private class Cur5[A: R, B: R, C: R, D: R, E: R, -Y, +Z](val a: A, val b: B, val c: C, val d: D, val e: E, val f: (A, B, C, D, E, Y) => Z) extends ReusableFn[Y, Z] { override def apply(y: Y): Z = f(a, b, c, d, e, y) - override private[extra] def reusable = { case x: Cur5[A, B, C, D, E, Y, Z] => (f eq x.f) && (a ~=~ x.a) && (b ~=~ x.b) && (c ~=~ x.c) && (d ~=~ x.d) && (e ~=~ x.e) } + override private[extra] def reusable[I <: Y, O >: Z] = { case x: Cur5[A, B, C, D, E, _, _] => (f eq x.f) && (a ~=~ x.a) && (b ~=~ x.b) && (c ~=~ x.c) && (d ~=~ x.d) && (e ~=~ x.e) } } } diff --git a/extra/src/main/scala/japgolly/scalajs/react/extra/package.scala b/extra/src/main/scala/japgolly/scalajs/react/extra/package.scala index 577ba331d..517ba37f2 100644 --- a/extra/src/main/scala/japgolly/scalajs/react/extra/package.scala +++ b/extra/src/main/scala/japgolly/scalajs/react/extra/package.scala @@ -10,7 +10,7 @@ package object extra { if (!test) dom.console.warn(msg) - type ~=>[A, B] = ReusableFn[A, B] + type ~=>[-A, +B] = ReusableFn[A, B] @inline implicit class ReactExtrasAnyExt[A](private val self: A) extends AnyVal { @inline def ~=~(a: A)(implicit r: Reusability[A]): Boolean = r.test(self, a) diff --git a/test/src/test/scala/japgolly/scalajs/react/TestUtil.scala b/test/src/test/scala/japgolly/scalajs/react/TestUtil.scala index ade6fedcb..12a6455e4 100644 --- a/test/src/test/scala/japgolly/scalajs/react/TestUtil.scala +++ b/test/src/test/scala/japgolly/scalajs/react/TestUtil.scala @@ -104,9 +104,17 @@ object TestUtil { def apply[B](f: A => B) = new { def expect[C](implicit ev: B =:= C): Unit = () def expect_<[C](implicit ev: B <:< C): Unit = () + def expect_>[C](implicit ev: C <:< B): Unit = () } + def expect_<[C](implicit ev: A <:< C): Unit = () + def expect_>[C](implicit ev: C <:< A): Unit = () + def usableAs[B](implicit ev: A => B): Unit = () } + trait Big + trait Medium <: Big + trait Small <: Medium + trait M[A] trait P trait S diff --git a/test/src/test/scala/japgolly/scalajs/react/extra/ReusableFnTest.scala b/test/src/test/scala/japgolly/scalajs/react/extra/ReusableFnTest.scala index 3459b7c3a..949c6732f 100644 --- a/test/src/test/scala/japgolly/scalajs/react/extra/ReusableFnTest.scala +++ b/test/src/test/scala/japgolly/scalajs/react/extra/ReusableFnTest.scala @@ -9,35 +9,58 @@ import TestUtil2._ object ReusableFnTest extends TestSuite { - type F1[A] = Int ~=> A - type F2[A] = Int ~=> F1[A] - type F3[A] = Int ~=> F2[A] + object Fs { + type F1[A] = Int ~=> A + type F2[A] = Int ~=> F1[A] + type F3[A] = Int ~=> F2[A] - def test1[A](f: F1[A], g: F1[A]): Unit = { - assert(f ~=~ f) - assert(f ~/~ g) - } + def test1[A](f: F1[A], g: F1[A]): Unit = { + assert(f ~=~ f) + assert(f ~/~ g) + } - def test2[A](f: F2[A], g: F2[A]): Unit = { - test1(f, g) - assert(f(1) ~=~ f(1)) - assert(f(1) ~/~ f(2)) - assert(f(1) ~/~ g(1)) + def test2[A](f: F2[A], g: F2[A]): Unit = { + test1(f, g) + assert(f(1) ~=~ f(1)) + assert(f(1) ~/~ f(2)) + assert(f(1) ~/~ g(1)) + } + + def test3[A](f: F3[A], g: F3[A]): Unit = { + test2(f, g) + assert(f(1)(2) ~=~ f(1)(2)) + assert(f(1)(2) ~/~ f(1)(3)) + assert(f(1)(2) ~/~ f(2)(2)) + assert(f(1)(2) ~/~ f(2)(1)) + assert(f(2)(1) ~=~ f(2)(1)) + assert(f(1)(2) ~/~ g(1)(2)) + } } - def test3[A](f: F3[A], g: F3[A]): Unit = { - test2(f, g) - assert(f(1)(2) ~=~ f(1)(2)) - assert(f(1)(2) ~/~ f(1)(3)) - assert(f(1)(2) ~/~ f(2)(2)) - assert(f(1)(2) ~/~ f(2)(1)) - assert(f(2)(1) ~=~ f(2)(1)) - assert(f(1)(2) ~/~ g(1)(2)) + object AIs { + sealed trait A { def i: Int } + case class I(i: Int) extends A + case object O extends A { override def i = 0 } + implicit def reuseA = Reusability.by_==[A] + + type F1 = I ~=> A // ← from A ~=> I + type F2 = I ~=> F1 + + def test1(f: => F1, g: => F1): Unit = { + assert(f ~=~ f) + assert(f ~/~ g) + } + + def test2(f: => F2, g: => F2): Unit = { + test1(f(I(1)), f(I(2))) + test1(f(I(1)), g(I(1))) + } } override def tests = TestSuite { 'fn1 { + import Fs._ val f = ReusableFn((i: Int) => i + 1) val g = ReusableFn((i: Int) => i + 10) test1(f, g) @@ -45,6 +68,7 @@ object ReusableFnTest extends TestSuite { } 'fn2 { + import Fs._ val f = ReusableFn((a: Int, b: Int) => a + b) val g = ReusableFn((a: Int, b: Int) => a * b) test2(f, g) @@ -52,6 +76,7 @@ object ReusableFnTest extends TestSuite { } 'fn3 { + import Fs._ val f = ReusableFn((a: Int, b: Int, c: Int) => a + b + c) val g = ReusableFn((a: Int, b: Int, c: Int) => a * b * c) test3(f, g) @@ -102,5 +127,77 @@ object ReusableFnTest extends TestSuite { assert(f3 ~/~ g.fnA(3)) assert(f3() == 6) } + + 'variance { + import TestUtil.Inference._ + + 'fn1 { + 'i { + compileError("test[Medium => Int].usableAs[Big => Int]") + compileError("test[Medium ~=> Int].usableAs[Big ~=> Int]") + test[Medium => Int].usableAs[Small => Int] + test[Medium ~=> Int].usableAs[Small ~=> Int] + } + 'o { + compileError("test[Int => Medium].usableAs[Int => Small]") + compileError("test[Int ~=> Medium].usableAs[Int ~=> Small]") + test[Int => Medium].usableAs[Int => Big] + test[Int ~=> Medium].usableAs[Int ~=> Big] + } + 'run { + import AIs._ + + def fai(add: Int): A ~=> I = + ReusableFn[A, I] { + case I(i) => I(i + add) + case O => I(0) + } + + val fai3 = fai(3) + val fai7 = fai(7) + def fia3: I ~=> A = fai3 + test1(fia3, fai7) + assert(fia3(I(2)) == I(5)) + } + } + + 'fn2 { + 'i1 { + compileError("test[Medium => (Int => Int)].usableAs[Big => (Int => Int)]") + compileError("test[Medium ~=> (Int ~=> Int)].usableAs[Big ~=> (Int ~=> Int)]") + test[Medium => (Int => Int)].usableAs[Small => (Int => Int)] + test[Medium ~=> (Int ~=> Int)].usableAs[Small ~=> (Int ~=> Int)] + } + 'i2 { + compileError("test[Int => (Medium => Int)].usableAs[Int => (Big => Int)]") + compileError("test[Int ~=> (Medium ~=> Int)].usableAs[Int ~=> (Big ~=> Int)]") + test[Int => (Medium => Int)].usableAs[Int => (Small => Int)] + test[Int ~=> (Medium ~=> Int)].usableAs[Int ~=> (Small ~=> Int)] + } + 'o { + compileError("test[Int => (Int => Medium)].usableAs[Int => (Int => Small)]") + compileError("test[Int ~=> (Int ~=> Medium)].usableAs[Int ~=> (Int ~=> Small)]") + test[Int => (Int => Medium)].usableAs[Int => (Int => Big )] + test[Int ~=> (Int ~=> Medium)].usableAs[Int ~=> (Int ~=> Big )] + } + 'run { + import AIs._ + + def faai(add: Int): A ~=> (A ~=> I) = + ReusableFn[A, A, I]((a, b) => b match { + case I(i) => I(a.i + i + add) + case O => I(a.i + 0) + }) + + val faai3 = faai(3) + val faai7 = faai(7) + def fiia3: I ~=> (I ~=> A) = faai3 + test2(fiia3, faai7) + assert(fiia3(I(2))(I(19)) == I(2 + 19 + 3)) + } + } + + } + } }