Skip to content

Commit 201765b

Browse files
committed
Consolidate ReusableFn into Reusable
1 parent a763b2b commit 201765b

File tree

11 files changed

+224
-231
lines changed

11 files changed

+224
-231
lines changed

doc/PERFORMANCE.md

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ libraryDependencies += "com.github.japgolly.scalajs-react" %%% "extra" % "0.11.3
2020
- [Usage](#usage)
2121
- [Example](#example)
2222
- [Monitoring](#monitoring)
23-
- [`ReusableFn`](#reusablefn)
23+
- [`Reusable.fn`](#reusablefn)
2424
- [Usage](#usage-1)
2525
- [Example](#example-1)
2626
- [Warning](#warning)
@@ -132,21 +132,21 @@ Usage:
132132
```
133133

134134

135-
`ReusableFn`
135+
`Reusable.fn`
136136
============
137137

138138
In effective usage of React, callbacks are passed around as component properties.
139139
Due to the ease of function creation in Scala it is often the case that functions are created inline and thus
140140
provide no means of determining whether a component can safely skip its update.
141141

142-
`ReusableFn` exists as a solution. It is a wrapper around a function that allows it to be both reused, and curried in a way that allows reuse.
142+
`Reusable.fn` exists as a solution. It is a wrapper around a function that allows it to be both reused, and curried in a way that allows reuse.
143143

144144
#### Usage
145145

146-
1. Just wrap `ReusableFn` around your function.
147-
2. Store the `ReusableFn` as a `val` somewhere outside of your `render` function, usually in the body of your backend class.
148-
3. Replace the callback (say `A => B`) in components' props, to take a `ReusableFn[A, B]` or the shorthand `A ~=> B`.
149-
4. Treat the `ReusableFn` as you would a normal function, save for one difference: application is curried (or Schönfinkel'ed), and each curried argument must have `Reusability`.
146+
1. Just wrap `Reusable.fn` around your function.
147+
2. Store the `Reusable.fn` as a `val` somewhere outside of your `render` function, usually in the body of your backend class.
148+
3. Replace the callback (say `A => B`) in components' props, to take a `Reusable.fn[A, B]` or the shorthand `A ~=> B`.
149+
4. Treat the `Reusable.fn` as you would a normal function, save for one difference: application is curried (or Schönfinkel'ed), and each curried argument must have `Reusability`.
150150

151151
#### Example
152152

@@ -164,7 +164,7 @@ val topComponent = ScalaComponent.build[State]("Demo")
164164

165165
class Backend($: BackendScope[_, State]) {
166166

167-
val updateUser = ReusableFn((id: PersonId, data: PersonData) => // ← Create a 2-arg fn
167+
val updateUser = Reusable.fn((id: PersonId, data: PersonData) => // ← Create a 2-arg fn
168168
$.modState(map => map.updated(id, data)))
169169

170170
def render(state: State) =
@@ -190,7 +190,7 @@ val personEditor = ScalaComponent.build[PersonEditorProps]("PersonEditor")
190190

191191
#### WARNING!
192192

193-
**DO NOT** feed the `ReusableFn(...)` constructor a function directly *derived* from a component's props or state.
193+
**DO NOT** feed the `Reusable.fn(...)` constructor a function directly *derived* from a component's props or state.
194194
Access to props/state on the right-hand side of the function args is ok but if the function itself is a result of the
195195
props/state, the function will forever be based on data that can go stale.
196196

@@ -200,25 +200,25 @@ Example:
200200
case class Props(person: StateSnapshot[Person], other: Other)
201201

202202
// THIS IS BAD!!
203-
ReusableFn($.props.runNow().person setL Props.name)
203+
Reusable.fn($.props.runNow().person setL Props.name)
204204

205205
// It is equivalent to:
206206
val g: String => Callback = $.props.runNow().person setL Person.name // ← $.props is evaluated once here
207-
val f: String ~=> Callback = ReusableFn(g) // ← …and never again.
207+
val f: String ~=> Callback = Reusable.fn(g) // ← …and never again.
208208
```
209209

210210
Alternatives:
211211

212-
1. Use `ReusableFn.byName`:
212+
1. Use `Reusable.fn.byName`:
213213

214214
```scala
215-
ReusableFn.byName($.props.runNow().person setL Person.name)
215+
Reusable.fn.byName($.props.runNow().person setL Person.name)
216216
```
217217

218218
2. Create a function with `$` on the right-hand side:
219219

220220
```scala
221-
ReusableFn(str => $.props.flatMap(_.person.setL(Person.name)(str)))
221+
Reusable.fn(str => $.props.flatMap(_.person.setL(Person.name)(str)))
222222
```
223223

224224

@@ -227,18 +227,18 @@ Alternatives:
227227
To cater for some common use cases, there are few convenience methods that are useful to know.
228228
For these examples imagine `$` to be your component's scope instance, eg. `BackendScope[_,S]`, `CompScope.Mounted[_,S,_,_]` or similar.
229229

230-
1. `ReusableFn($).{set,mod}State`.
230+
1. `Reusable.fn($).{set,mod}State`.
231231

232-
You'll find that if you try `ReusableFn($.method)` Scala will fail to infer the correct types.
233-
Use `ReusableFn($).method` instead to get the types that you expect.
232+
You'll find that if you try `Reusable.fn($.method)` Scala will fail to infer the correct types.
233+
Use `Reusable.fn($).method` instead to get the types that you expect.
234234

235-
Example: instead of `ReusableFn($.setState)` use `ReusableFn.state($).set` and you will correctly get a `S ~=> Callback`.
235+
Example: instead of `Reusable.fn($.setState)` use `Reusable.fn.state($).set` and you will correctly get a `S ~=> Callback`.
236236

237-
2. `ReusableFn.endo____`.
237+
2. `Reusable.fn.endo____`.
238238

239-
Anytime the input to your `ReusableFn` is an endofunction (`A => A`), additional methods starting with `endo` become available.
239+
Anytime the input to your `Reusable.fn` is an endofunction (`A => A`), additional methods starting with `endo` become available.
240240

241-
Specifically, `ReusableFn.state($).mod` returns a `(S => S) ~=> Callback` which you will often want to transform.
241+
Specifically, `Reusable.fn.state($).mod` returns a `(S => S) ~=> Callback` which you will often want to transform.
242242
These examples would be available on an `(S => S) ~=> U`:
243243

244244
* `endoCall (S => (A => S)): A ~=> U` - Call a 1-arg function on `S`.
@@ -253,19 +253,19 @@ For these examples imagine `$` to be your component's scope instance, eg. `Backe
253253

254254
// Manual long-hand
255255
val long: Int ~=> (String ~=> Callback) =
256-
ReusableFn((id: Int, data: String) => $.modState(map => map.updated(id, data)))
256+
Reusable.fn((id: Int, data: String) => $.modState(map => map.updated(id, data)))
257257

258258
// Shorter using helpers described above
259259
val short: Int ~=> (String ~=> Callback) =
260-
ReusableFn.state($).mod.endoCall2(_.updated)
260+
Reusable.fn.state($).mod.endoCall2(_.updated)
261261
```
262262

263-
3. `ReusableFn($ zoomStateL lens)`
263+
3. `Reusable.fn($ zoomStateL lens)`
264264

265265
Lenses provide an abstraction over read-and-write field access.
266266
Using Monocle, you can annotate your case classes with `@Lenses` to gain automatic lenses.
267267
`$ zoomStateL lens` will then narrow the scope of its state to the field targeted by the given lens.
268-
This can then be used with `ReusableFn` as follows:
268+
This can then be used with `Reusable.fn` as follows:
269269

270270
```scala
271271
@Lenses
@@ -274,7 +274,7 @@ For these examples imagine `$` to be your component's scope instance, eg. `Backe
274274
class Backend($: BackendScope[_, Person]) {
275275

276276
val nameSetter: String ~=> Callback =
277-
ReusableFn($ zoomL Person.name).setState
277+
Reusable.fn($ zoomL Person.name).setState
278278
```
279279

280280

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

Lines changed: 175 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package japgolly.scalajs.react.extra
22

33
import scala.reflect.ClassTag
4+
import japgolly.scalajs.react.{Callback, StateAccessor}
45

56
final class Reusable[+A] private[Reusable](valueByNeed: () => A,
67
private[Reusable] val root: Any,
@@ -25,6 +26,9 @@ final class Reusable[+A] private[Reusable](valueByNeed: () => A,
2526

2627
object Reusable {
2728

29+
@inline implicit def autoValue[A](r: Reusable[A]): A =
30+
r.value
31+
2832
private def root[A](a: A, isReusable: Reusable[Any] => Boolean): Reusable[A] =
2933
new Reusable[A](() => a, a, isReusable)
3034

@@ -72,5 +76,175 @@ object Reusable {
7276

7377
implicit def reusableReusability[A]: Reusability[Reusable[A]] =
7478
reusabilityInstance.narrow
75-
}
7679

80+
// ===================================================================================================================
81+
82+
/**
83+
* A function that facilitates stability and reuse.
84+
*
85+
* In effective usage of React, callbacks are passed around as component properties.
86+
* Due to the ease of function creation in Scala it is often the case that functions are created inline and thus
87+
* provide no means of determining whether a component can safely skip its update.
88+
* This exists as a solution.
89+
*
90+
* @since 0.9.0
91+
*/
92+
object fn {
93+
import FnInternals._
94+
95+
def apply[Y, Z](f: Y => Z): Y ~=> Z =
96+
Reusable.implicitly(new Fn1(f))
97+
98+
def apply[A: Reusability, Y, Z](f: (A, Y) => Z): A ~=> (Y ~=> Z) =
99+
Reusable.implicitly(new Fn2(f))
100+
101+
def apply[A: Reusability, B: Reusability, Y, Z](f: (A, B, Y) => Z): A ~=> (B ~=> (Y ~=> Z)) =
102+
Reusable.implicitly(new Fn3(f))
103+
104+
def apply[A: Reusability, B: Reusability, C: Reusability, Y, Z](f: (A, B, C, Y) => Z): A ~=> (B ~=> (C ~=> (Y ~=> Z))) =
105+
Reusable.implicitly(new Fn4(f))
106+
107+
def apply[A: Reusability, B: Reusability, C: Reusability, D: Reusability, Y, Z](f: (A, B, C, D, Y) => Z): A ~=> (B ~=> (C ~=> (D ~=> (Y ~=> Z)))) =
108+
Reusable.implicitly(new Fn5(f))
109+
110+
def apply[A: Reusability, B: Reusability, C: Reusability, D: Reusability, E: Reusability, Y, Z](f: (A, B, C, D, E, Y) => Z): A ~=> (B ~=> (C ~=> (D ~=> (E ~=> (Y ~=> Z))))) =
111+
Reusable.implicitly(new Fn6(f))
112+
113+
def state[I, S](i: I)(implicit t: StateAccessor.WriteCB[I, S]) = new StateAccessWriteOps(i)(t)
114+
final class StateAccessWriteOps[I, S](i: I)(implicit t: StateAccessor.WriteCB[I, S]) {
115+
116+
def mod: (S => S) ~=> Callback =
117+
Reusable.fn(t modState i)
118+
119+
def set: S ~=> Callback =
120+
Reusable.fn(t setState i)
121+
}
122+
}
123+
124+
// ===================================================================================================================
125+
126+
private object FnInternals {
127+
private type R[A] = Reusability[A]
128+
type ReusableFn[-A, +B] = scala.runtime.AbstractFunction1[A, B]
129+
130+
// -------------------------------------------------------------------------
131+
132+
class Fn1[-Y, +Z](val f: Y => Z) extends ReusableFn[Y, Z] {
133+
override def apply(a: Y) = f(a)
134+
}
135+
136+
private val _reusabilityFn1: Reusability[Fn1[_, _]] =
137+
Reusability((x, y) => (x eq y) || (x.f eq y.f))
138+
139+
implicit def reusabilityFn1[Y, Z]: Reusability[Fn1[Y, Z]] =
140+
_reusabilityFn1.narrow
141+
142+
// -------------------------------------------------------------------------
143+
144+
class Fn2[A: R, -Y, +Z](val f: (A, Y) => Z) extends ReusableFn[A, Y ~=> Z] {
145+
override def apply(a: A) = Reusable.implicitly(new Cur2(a, f))
146+
}
147+
148+
class Cur2[A, -Y, +Z](val a: A, val f: (A, Y) => Z) extends ReusableFn[Y, Z] {
149+
override def apply(y: Y): Z = f(a, y)
150+
}
151+
152+
private val _reusabilityFn2: Reusability[Fn2[_, _, _]] =
153+
Reusability((x, y) => (x eq y) || (x.f eq y.f))
154+
155+
implicit def reusabilityFn2[A, Y, Z]: Reusability[Fn2[A, Y, Z]] =
156+
_reusabilityFn2.narrow
157+
158+
implicit def reusabilityCur2[A: R, Y, Z]: Reusability[Cur2[A, Y, Z]] =
159+
Reusability((x, y) => (x eq y) || ((x.f eq y.f) && (x.a ~=~ y.a)))
160+
161+
// -------------------------------------------------------------------------
162+
163+
class Fn3[A: R, B: R, -Y, +Z](val f: (A, B, Y) => Z) extends ReusableFn[A, B ~=> (Y ~=> Z)] {
164+
private val c2 = cur3(f)
165+
override def apply(a: A) = Reusable.implicitly(new Cur2(a, c2))
166+
}
167+
168+
def cur3[A: R, B: R, Y, Z](f: (A, B, Y) => Z): (A, B) => (Y ~=> Z) =
169+
(a, b) => Reusable.implicitly(new Cur3(a, b, f))
170+
171+
class Cur3[A, B, -Y, +Z](val a: A, val b: B, val f: (A, B, Y) => Z) extends ReusableFn[Y, Z] {
172+
override def apply(y: Y): Z = f(a, b, y)
173+
}
174+
175+
private val _reusabilityFn3: Reusability[Fn3[_, _, _, _]] =
176+
Reusability((x, y) => (x eq y) || (x.f eq y.f))
177+
178+
implicit def reusabilityFn3[A, B, Y, Z]: Reusability[Fn3[A, B, Y, Z]] =
179+
_reusabilityFn3.narrow
180+
181+
implicit def reusabilityCur3[A: R, B: R, Y, Z]: Reusability[Cur3[A, B, Y, Z]] =
182+
Reusability((x, y) => (x eq y) || ((x.f eq y.f) && (x.a ~=~ y.a) && (x.b ~=~ y.b)))
183+
184+
// -------------------------------------------------------------------------
185+
186+
class Fn4[A: R, B: R, C: R, -Y, +Z](val f: (A, B, C, Y) => Z) extends ReusableFn[A, B ~=> (C ~=> (Y ~=> Z))] {
187+
private val c3 = cur4(f)
188+
private val c2 = cur3(c3)
189+
override def apply(a: A) = Reusable.implicitly(new Cur2(a, c2))
190+
}
191+
192+
def cur4[A: R, B: R, C: R, Y, Z](f: (A, B, C, Y) => Z): (A, B, C) => (Y ~=> Z) =
193+
(a, b, c) => Reusable.implicitly(new Cur4(a, b, c, f))
194+
195+
class Cur4[A, B, C, -Y, +Z](val a: A, val b: B, val c: C, val f: (A, B, C, Y) => Z) extends ReusableFn[Y, Z] {
196+
override def apply(y: Y): Z = f(a, b, c, y)
197+
}
198+
199+
implicit def reusabilityFn4[A, B, C, Y, Z]: Reusability[Fn4[A, B, C, Y, Z]] =
200+
Reusability((x, y) => (x eq y) || (x.f eq y.f))
201+
202+
implicit def reusabilityCur4[A: R, B: R, C: R, Y, Z]: Reusability[Cur4[A, B, C, Y, Z]] =
203+
Reusability((x, y) => (x eq y) || ((x.f eq y.f) && (x.a ~=~ y.a) && (x.b ~=~ y.b) && (x.c ~=~ y.c)))
204+
205+
// -------------------------------------------------------------------------
206+
207+
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)))] {
208+
private val c4 = cur5(f)
209+
private val c3 = cur4(c4)
210+
private val c2 = cur3(c3)
211+
override def apply(a: A) = Reusable.implicitly(new Cur2(a, c2))
212+
}
213+
214+
def cur5[A: R, B: R, C: R, D: R, Y, Z](f: (A, B, C, D, Y) => Z): (A, B, C, D) => (Y ~=> Z) =
215+
(a, b, c, d) => Reusable.implicitly(new Cur5(a, b, c, d, f))
216+
217+
class Cur5[A, B, C, D, -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] {
218+
override def apply(y: Y): Z = f(a, b, c, d, y)
219+
}
220+
221+
implicit def reusabilityFn5[A, B, C, D, Y, Z]: Reusability[Fn5[A, B, C, D, Y, Z]] =
222+
Reusability((x, y) => (x eq y) || (x.f eq y.f))
223+
224+
implicit def reusabilityCur5[A: R, B: R, C: R, D: R, Y, Z]: Reusability[Cur5[A, B, C, D, Y, Z]] =
225+
Reusability((x, y) => (x eq y) || ((x.f eq y.f) && (x.a ~=~ y.a) && (x.b ~=~ y.b) && (x.c ~=~ y.c) && (x.d ~=~ y.d)))
226+
227+
// -------------------------------------------------------------------------
228+
229+
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))))] {
230+
private val c5 = cur6(f)
231+
private val c4 = cur5(c5)
232+
private val c3 = cur4(c4)
233+
private val c2 = cur3(c3)
234+
override def apply(a: A) = Reusable.implicitly(new Cur2(a, c2))
235+
}
236+
237+
def cur6[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) =
238+
(a, b, c, d, e) => Reusable.implicitly(new Cur6(a, b, c, d, e, f))
239+
240+
class Cur6[A, B, C, D, E, -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] {
241+
override def apply(y: Y): Z = f(a, b, c, d, e, y)
242+
}
243+
244+
implicit def reusabilityFn6[A, B, C, D, E, Y, Z]: Reusability[Fn6[A, B, C, D, E, Y, Z]] =
245+
Reusability((x, y) => (x eq y) || (x.f eq y.f))
246+
247+
implicit def reusabilityCur6[A: R, B: R, C: R, D: R, E: R, Y, Z]: Reusability[Cur6[A, B, C, D, E, Y, Z]] =
248+
Reusability((x, y) => (x eq y) || ((x.f eq y.f) && (x.a ~=~ y.a) && (x.b ~=~ y.b) && (x.c ~=~ y.c) && (x.d ~=~ y.d) && (x.e ~=~ y.e)))
249+
}
250+
}

0 commit comments

Comments
 (0)