Skip to content

Commit c5dc9ec

Browse files
committed
Merge pull request #276 from japgolly/topic/reusableFnVariance
ReusableFn variance
2 parents 0f2c0a2 + a04e776 commit c5dc9ec

File tree

5 files changed

+154
-48
lines changed

5 files changed

+154
-48
lines changed

doc/changelog/0.11.1.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## 0.11.1
22

3+
* `extra.ReusableFn` is now contravariant in inputs and covariant in outputs like normal functions.
34
* Upgrade Monocle to 1.2.1, for official Scala.JS support.
45
* Upgrade Scalaz 7.2.{1 ⇒ 2}.
56

extra/src/main/scala/japgolly/scalajs/react/extra/ReusableFn.scala

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import japgolly.scalajs.react._
1313
*
1414
* @since 0.9.0
1515
*/
16-
sealed abstract class ReusableFn[A, B] extends AbstractFunction1[A, B] {
17-
private[extra] def reusable: PartialFunction[ReusableFn[A, B], Boolean]
16+
sealed abstract class ReusableFn[-A, +B] extends AbstractFunction1[A, B] {
17+
private[extra] def reusable[AA <: A, BB >: B]: PartialFunction[AA ~=> BB, Boolean]
1818

19-
def asVar(value: A)(implicit r: Reusability[A], ev: ReusableFn[A, B] =:= ReusableFn[A, Callback]): ReusableVar[A] =
19+
def asVar[AA <: A](value: AA)(implicit r: Reusability[AA], ev: (A ~=> B) <:< (AA ~=> Callback)): ReusableVar[AA] =
2020
new ReusableVar(value, ev(this))(r)
2121

22-
def asVarR(value: A, r: Reusability[A])(implicit ev: ReusableFn[A, B] =:= ReusableFn[A, Callback]): ReusableVar[A] =
22+
def asVarR[AA <: A](value: AA, r: Reusability[AA])(implicit ev: (A ~=> B) <:< (AA ~=> Callback)): ReusableVar[AA] =
2323
asVar(value)(r, ev)
2424

2525
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] {
3131
def contramap[C](f: C => A): C ~=> B =
3232
dimap(f.andThen)
3333

34-
def fnA(a: A)(implicit ra: Reusability[A]): ReusableFnA[A, B] =
34+
def fnA[AA <: A, BB >: B](a: AA)(implicit ra: Reusability[AA]): ReusableFnA[AA, BB] =
3535
new ReusableFnA(a, this)
3636
}
3737

@@ -98,74 +98,74 @@ object ReusableFn {
9898
// ===================================================================================================================
9999
private type R[A] = Reusability[A]
100100

101-
private class Fn1[Y, Z](val f: Y => Z) extends ReusableFn[Y, Z] {
101+
private class Fn1[-Y, +Z](val f: Y => Z) extends ReusableFn[Y, Z] {
102102
override def apply(a: Y) = f(a)
103-
override private[extra] def reusable = { case x: Fn1[Y, Z] => f eq x.f }
103+
override private[extra] def reusable[I <: Y, O >: Z] = { case x: Fn1[I, O] => f eq x.f }
104104
}
105105

106-
private class Fn2[A: R, Y, Z](val f: (A, Y) => Z) extends ReusableFn[A, Y ~=> Z] {
106+
private class Fn2[A: R, -Y, +Z](val f: (A, Y) => Z) extends ReusableFn[A, Y ~=> Z] {
107107
override def apply(a: A) = new Cur1(a, f)
108-
override private[extra] def reusable = { case x: Fn2[A, Y, Z] => f eq x.f }
108+
override private[extra] def reusable[I <: A, O >: (Y ~=> Z)] = { case x: Fn2[I, _, _] => f eq x.f }
109109
}
110110

111-
private class Fn3[A: R, B: R, Y, Z](val f: (A, B, Y) => Z) extends ReusableFn[A, B ~=> (Y ~=> Z)] {
111+
private class Fn3[A: R, B: R, -Y, +Z](val f: (A, B, Y) => Z) extends ReusableFn[A, B ~=> (Y ~=> Z)] {
112112
private val c2 = cur2(f)
113113
override def apply(a: A) = new Cur1(a, c2)
114-
override private[extra] def reusable = { case x: Fn3[A, B, Y, Z] => f eq x.f }
114+
override private[extra] def reusable[I <: A, O >: (B ~=> (Y ~=> Z))] = { case x: Fn3[I, _, _, _] => f eq x.f }
115115
}
116116

117-
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))] {
117+
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))] {
118118
private val c3 = cur3(f)
119119
private val c2 = cur2(c3)
120120
override def apply(a: A) = new Cur1(a, c2)
121-
override private[extra] def reusable = { case x: Fn4[A, B, C, Y, Z] => f eq x.f }
121+
override private[extra] def reusable[I <: A, O >: (B ~=> (C ~=> (Y ~=> Z)))] = { case x: Fn4[I, _, _, _, _] => f eq x.f }
122122
}
123123

124-
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)))] {
124+
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)))] {
125125
private val c4 = cur4(f)
126126
private val c3 = cur3(c4)
127127
private val c2 = cur2(c3)
128128
override def apply(a: A) = new Cur1(a, c2)
129-
override private[extra] def reusable = { case x: Fn5[A, B, C, D, Y, Z] => f eq x.f }
129+
override private[extra] def reusable[I <: A, O >: (B ~=> (C ~=> (D ~=> (Y ~=> Z))))] = { case x: Fn5[I, _, _, _, _, _] => f eq x.f }
130130
}
131131

132-
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))))] {
132+
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))))] {
133133
private val c5 = cur5(f)
134134
private val c4 = cur4(c5)
135135
private val c3 = cur3(c4)
136136
private val c2 = cur2(c3)
137137
override def apply(a: A) = new Cur1(a, c2)
138-
override private[extra] def reusable = { case x: Fn6[A, B, C, D, E, Y, Z] => f eq x.f }
138+
override private[extra] def reusable[I <: A, O >: (B ~=> (C ~=> (D ~=> (E ~=> (Y ~=> Z)))))] = { case x: Fn6[I, _, _, _, _, _, _] => f eq x.f }
139139
}
140140

141141
@inline private def cur2[A:R, B:R, Y, Z](f: (A,B, Y) => Z): (A,B ) => (Y ~=> Z) = new Cur2(_,_, f)
142142
@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)
143143
@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)
144144
@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)
145145

146-
private class Cur1[A: R, Y, Z](val a: A, val f: (A, Y) => Z) extends ReusableFn[Y, Z] {
146+
private class Cur1[A: R, -Y, +Z](val a: A, val f: (A, Y) => Z) extends ReusableFn[Y, Z] {
147147
override def apply(y: Y): Z = f(a, y)
148-
override private[extra] def reusable = { case x: Cur1[A, Y, Z] => (f eq x.f) && (a ~=~ x.a) }
148+
override private[extra] def reusable[I <: Y, O >: Z] = { case x: Cur1[A, _, _] => (f eq x.f) && (a ~=~ x.a) }
149149
}
150150

151-
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] {
151+
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] {
152152
override def apply(y: Y): Z = f(a, b, y)
153-
override private[extra] def reusable = { case x: Cur2[A, B, Y, Z] => (f eq x.f) && (a ~=~ x.a) && (b ~=~ x.b) }
153+
override private[extra] def reusable[I <: Y, O >: Z] = { case x: Cur2[A, B, _, _] => (f eq x.f) && (a ~=~ x.a) && (b ~=~ x.b) }
154154
}
155155

156-
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] {
156+
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] {
157157
override def apply(y: Y): Z = f(a, b, c, y)
158-
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) }
158+
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) }
159159
}
160160

161-
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] {
161+
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] {
162162
override def apply(y: Y): Z = f(a, b, c, d, y)
163-
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) }
163+
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) }
164164
}
165165

166-
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] {
166+
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] {
167167
override def apply(y: Y): Z = f(a, b, c, d, e, y)
168-
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) }
168+
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) }
169169
}
170170
}
171171

extra/src/main/scala/japgolly/scalajs/react/extra/package.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ package object extra {
1010
if (!test)
1111
dom.console.warn(msg)
1212

13-
type ~=>[A, B] = ReusableFn[A, B]
13+
type ~=>[-A, +B] = ReusableFn[A, B]
1414

1515
@inline implicit class ReactExtrasAnyExt[A](private val self: A) extends AnyVal {
1616
@inline def ~=~(a: A)(implicit r: Reusability[A]): Boolean = r.test(self, a)

test/src/test/scala/japgolly/scalajs/react/TestUtil.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,17 @@ object TestUtil {
104104
def apply[B](f: A => B) = new {
105105
def expect[C](implicit ev: B =:= C): Unit = ()
106106
def expect_<[C](implicit ev: B <:< C): Unit = ()
107+
def expect_>[C](implicit ev: C <:< B): Unit = ()
107108
}
109+
def expect_<[C](implicit ev: A <:< C): Unit = ()
110+
def expect_>[C](implicit ev: C <:< A): Unit = ()
111+
def usableAs[B](implicit ev: A => B): Unit = ()
108112
}
109113

114+
trait Big
115+
trait Medium <: Big
116+
trait Small <: Medium
117+
110118
trait M[A]
111119
trait P
112120
trait S

test/src/test/scala/japgolly/scalajs/react/extra/ReusableFnTest.scala

Lines changed: 117 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,49 +9,74 @@ import TestUtil2._
99

1010
object ReusableFnTest extends TestSuite {
1111

12-
type F1[A] = Int ~=> A
13-
type F2[A] = Int ~=> F1[A]
14-
type F3[A] = Int ~=> F2[A]
12+
object Fs {
13+
type F1[A] = Int ~=> A
14+
type F2[A] = Int ~=> F1[A]
15+
type F3[A] = Int ~=> F2[A]
1516

16-
def test1[A](f: F1[A], g: F1[A]): Unit = {
17-
assert(f ~=~ f)
18-
assert(f ~/~ g)
19-
}
17+
def test1[A](f: F1[A], g: F1[A]): Unit = {
18+
assert(f ~=~ f)
19+
assert(f ~/~ g)
20+
}
2021

21-
def test2[A](f: F2[A], g: F2[A]): Unit = {
22-
test1(f, g)
23-
assert(f(1) ~=~ f(1))
24-
assert(f(1) ~/~ f(2))
25-
assert(f(1) ~/~ g(1))
22+
def test2[A](f: F2[A], g: F2[A]): Unit = {
23+
test1(f, g)
24+
assert(f(1) ~=~ f(1))
25+
assert(f(1) ~/~ f(2))
26+
assert(f(1) ~/~ g(1))
27+
}
28+
29+
def test3[A](f: F3[A], g: F3[A]): Unit = {
30+
test2(f, g)
31+
assert(f(1)(2) ~=~ f(1)(2))
32+
assert(f(1)(2) ~/~ f(1)(3))
33+
assert(f(1)(2) ~/~ f(2)(2))
34+
assert(f(1)(2) ~/~ f(2)(1))
35+
assert(f(2)(1) ~=~ f(2)(1))
36+
assert(f(1)(2) ~/~ g(1)(2))
37+
}
2638
}
2739

28-
def test3[A](f: F3[A], g: F3[A]): Unit = {
29-
test2(f, g)
30-
assert(f(1)(2) ~=~ f(1)(2))
31-
assert(f(1)(2) ~/~ f(1)(3))
32-
assert(f(1)(2) ~/~ f(2)(2))
33-
assert(f(1)(2) ~/~ f(2)(1))
34-
assert(f(2)(1) ~=~ f(2)(1))
35-
assert(f(1)(2) ~/~ g(1)(2))
40+
object AIs {
41+
sealed trait A { def i: Int }
42+
case class I(i: Int) extends A
43+
case object O extends A { override def i = 0 }
44+
implicit def reuseA = Reusability.by_==[A]
45+
46+
type F1 = I ~=> A // ← from A ~=> I
47+
type F2 = I ~=> F1
48+
49+
def test1(f: => F1, g: => F1): Unit = {
50+
assert(f ~=~ f)
51+
assert(f ~/~ g)
52+
}
53+
54+
def test2(f: => F2, g: => F2): Unit = {
55+
test1(f(I(1)), f(I(2)))
56+
test1(f(I(1)), g(I(1)))
57+
}
3658
}
3759

3860
override def tests = TestSuite {
3961

4062
'fn1 {
63+
import Fs._
4164
val f = ReusableFn((i: Int) => i + 1)
4265
val g = ReusableFn((i: Int) => i + 10)
4366
test1(f, g)
4467
assert(f(5) == 6)
4568
}
4669

4770
'fn2 {
71+
import Fs._
4872
val f = ReusableFn((a: Int, b: Int) => a + b)
4973
val g = ReusableFn((a: Int, b: Int) => a * b)
5074
test2(f, g)
5175
assert(f(1)(2) == 3)
5276
}
5377

5478
'fn3 {
79+
import Fs._
5580
val f = ReusableFn((a: Int, b: Int, c: Int) => a + b + c)
5681
val g = ReusableFn((a: Int, b: Int, c: Int) => a * b * c)
5782
test3(f, g)
@@ -102,5 +127,77 @@ object ReusableFnTest extends TestSuite {
102127
assert(f3 ~/~ g.fnA(3))
103128
assert(f3() == 6)
104129
}
130+
131+
'variance {
132+
import TestUtil.Inference._
133+
134+
'fn1 {
135+
'i {
136+
compileError("test[Medium => Int].usableAs[Big => Int]")
137+
compileError("test[Medium ~=> Int].usableAs[Big ~=> Int]")
138+
test[Medium => Int].usableAs[Small => Int]
139+
test[Medium ~=> Int].usableAs[Small ~=> Int]
140+
}
141+
'o {
142+
compileError("test[Int => Medium].usableAs[Int => Small]")
143+
compileError("test[Int ~=> Medium].usableAs[Int ~=> Small]")
144+
test[Int => Medium].usableAs[Int => Big]
145+
test[Int ~=> Medium].usableAs[Int ~=> Big]
146+
}
147+
'run {
148+
import AIs._
149+
150+
def fai(add: Int): A ~=> I =
151+
ReusableFn[A, I] {
152+
case I(i) => I(i + add)
153+
case O => I(0)
154+
}
155+
156+
val fai3 = fai(3)
157+
val fai7 = fai(7)
158+
def fia3: I ~=> A = fai3
159+
test1(fia3, fai7)
160+
assert(fia3(I(2)) == I(5))
161+
}
162+
}
163+
164+
'fn2 {
165+
'i1 {
166+
compileError("test[Medium => (Int => Int)].usableAs[Big => (Int => Int)]")
167+
compileError("test[Medium ~=> (Int ~=> Int)].usableAs[Big ~=> (Int ~=> Int)]")
168+
test[Medium => (Int => Int)].usableAs[Small => (Int => Int)]
169+
test[Medium ~=> (Int ~=> Int)].usableAs[Small ~=> (Int ~=> Int)]
170+
}
171+
'i2 {
172+
compileError("test[Int => (Medium => Int)].usableAs[Int => (Big => Int)]")
173+
compileError("test[Int ~=> (Medium ~=> Int)].usableAs[Int ~=> (Big ~=> Int)]")
174+
test[Int => (Medium => Int)].usableAs[Int => (Small => Int)]
175+
test[Int ~=> (Medium ~=> Int)].usableAs[Int ~=> (Small ~=> Int)]
176+
}
177+
'o {
178+
compileError("test[Int => (Int => Medium)].usableAs[Int => (Int => Small)]")
179+
compileError("test[Int ~=> (Int ~=> Medium)].usableAs[Int ~=> (Int ~=> Small)]")
180+
test[Int => (Int => Medium)].usableAs[Int => (Int => Big )]
181+
test[Int ~=> (Int ~=> Medium)].usableAs[Int ~=> (Int ~=> Big )]
182+
}
183+
'run {
184+
import AIs._
185+
186+
def faai(add: Int): A ~=> (A ~=> I) =
187+
ReusableFn[A, A, I]((a, b) => b match {
188+
case I(i) => I(a.i + i + add)
189+
case O => I(a.i + 0)
190+
})
191+
192+
val faai3 = faai(3)
193+
val faai7 = faai(7)
194+
def fiia3: I ~=> (I ~=> A) = faai3
195+
test2(fiia3, faai7)
196+
assert(fiia3(I(2))(I(19)) == I(2 + 19 + 3))
197+
}
198+
}
199+
200+
}
201+
105202
}
106203
}

0 commit comments

Comments
 (0)