Skip to content

Commit

Permalink
Merge pull request #276 from japgolly/topic/reusableFnVariance
Browse files Browse the repository at this point in the history
ReusableFn variance
  • Loading branch information
japgolly committed Apr 22, 2016
2 parents 0f2c0a2 + a04e776 commit c5dc9ec
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 48 deletions.
1 change: 1 addition & 0 deletions doc/changelog/0.11.1.md
Original file line number Diff line number Diff line change
@@ -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}.

54 changes: 27 additions & 27 deletions extra/src/main/scala/japgolly/scalajs/react/extra/ReusableFn.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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)
}

Expand Down Expand Up @@ -98,74 +98,74 @@ 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)
@inline private def cur3[A:R, B:R, C:R, Y, Z](f: (A,B,C, Y) => Z): (A,B,C ) => (Y ~=> Z) = new Cur3(_,_,_, f)
@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) }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions test/src/test/scala/japgolly/scalajs/react/TestUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
137 changes: 117 additions & 20 deletions test/src/test/scala/japgolly/scalajs/react/extra/ReusableFnTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,74 @@ 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)
assert(f(5) == 6)
}

'fn2 {
import Fs._
val f = ReusableFn((a: Int, b: Int) => a + b)
val g = ReusableFn((a: Int, b: Int) => a * b)
test2(f, g)
assert(f(1)(2) == 3)
}

'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)
Expand Down Expand Up @@ -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))
}
}

}

}
}

0 comments on commit c5dc9ec

Please sign in to comment.