Skip to content

Commit 1dda8cb

Browse files
authored
Merge pull request #1005 from japgolly/topic/effectSyntax
Add implicit syntax for generic effects
2 parents 0ab4fc3 + 0ae26b7 commit 1dda8cb

File tree

5 files changed

+181
-1
lines changed

5 files changed

+181
-1
lines changed

doc/FX_AGNOSTICISM.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ friends directly, but if you want your library to be effect-agnostic then follow
1414
* [Producing effects from classes](#producing-effects-from-classes)
1515
* [Producing effects from traits](#producing-effects-from-traits)
1616
* [Modifying effects](#modifying-effects)
17+
* [Implicit syntax/ops](#implicit-syntaxops)
1718

1819

1920
# Required sbt changes
@@ -299,8 +300,28 @@ This can be even more abstract if you want, no need to restrict it to only synch
299300
import japgolly.scalajs.react.util.Effect.Monad
300301

301302
def newWay[F[_], A](f: F[A])(implicit F: Monad[F]): F[Option[A]] = {
302-
// no implicit ops lol. This case is rare and I don't want to needlessly add to output JS size
303+
// no implicit ops in this example so that it doesn't needlessly add to output JS size.
304+
// implicit ops are exactly available though, see the it's section in this doc.
303305
val fo = F.map(f)(Option(_))
304306
F.flatmap(fo)(o => F.delay { println("Result is " + o); o })
305307
}
306308
```
309+
310+
311+
# Implicit syntax/ops
312+
313+
Implicit ops have now been added to make working with general effects easier.
314+
315+
```scala
316+
import japgolly.scalajs.react.util.Effect
317+
import japgolly.scalajs.react.util.syntax._
318+
319+
def example(fi: F[Int])(implicit F: Effect.Sync[F]): F[Int] =
320+
for {
321+
i <- fi
322+
j <- F.delay(123)
323+
} yield {
324+
println("Re-running fi = " + fi.runSync())
325+
i + j
326+
}
327+
```

doc/changelog/2.0.0-RCs.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,11 @@ Contents:
8888
* `CallbackOption#finallyRun[B](runFinally: CallbackOption[B]): CallbackOption[A]`
8989
* `CallbackOption#when_`
9090
* `CallbackOption#unless_`
91+
9192
* Add to cats module:
9293
* Implicit `MonadThrow` instances for scalajs-react effect types
9394
* Implicit `Monoid` instances for scalajs-react effect types with a monoidal value
95+
96+
* Add ops for working with generic effects via `import japgolly.scalajs.react.util.syntax._`
97+
9498
* Refactoring around internal effect-agnosticism type class definitions
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package japgolly.scalajs.react.util
2+
3+
import scala.scalajs.js
4+
5+
trait EffectSyntax
6+
extends EffectSyntax.ForAsync
7+
with EffectSyntax.ForSync
8+
9+
object EffectSyntax {
10+
import Effect._
11+
12+
trait ForMonad {
13+
implicit def sjrEffectMonadOps[F[_], A](fa: F[A])(implicit f: Monad[F]): MonadOps[F, A] =
14+
new MonadOps[F, A] {
15+
override protected def F = f
16+
override protected def self = fa
17+
}
18+
}
19+
20+
trait MonadOps[F[_], A] {
21+
protected def F: Monad[F]
22+
protected def self: F[A]
23+
24+
def map [B](f: A => B) : F[B] = F.map(self)(f)
25+
def flatMap[B](f: A => F[B]): F[B] = F.flatMap(self)(f)
26+
def >> [B](fb: F[B]) : F[B] = F.chain(self, fb)
27+
}
28+
29+
// ===================================================================================================================
30+
31+
trait ForEffect extends ForMonad {
32+
implicit def sjrEffectEffectOps[F[_], A](fa: F[A])(implicit f: Effect[F]): EffectOps[F, A] =
33+
new EffectOps[F, A] {
34+
override protected def F = f
35+
override protected def self = fa
36+
}
37+
}
38+
39+
trait EffectOps[F[_], A] extends MonadOps[F, A] {
40+
override protected def F: Effect[F]
41+
42+
def finallyRun [B] (runFinally: => F[B]) : F[A] = F.finallyRun(self, runFinally)
43+
def handleError[AA >: A](f: Throwable => F[AA]): F[AA] = F.handleError[A, AA](self)(f)
44+
}
45+
46+
// ===================================================================================================================
47+
48+
trait ForDispatch extends ForEffect {
49+
implicit def sjrEffectDispatchOps[F[_], A](fa: F[A])(implicit f: Dispatch[F]): DispatchOps[F, A] =
50+
new DispatchOps[F, A] {
51+
override protected def F = f
52+
override protected def self = fa
53+
}
54+
}
55+
56+
trait DispatchOps[F[_], A] extends EffectOps[F, A] {
57+
override protected def F: Dispatch[F]
58+
59+
def dispatch() : Unit = F.dispatch(self)
60+
def toDispatchFn: js.Function0[Unit] = F.dispatchFn(self)
61+
}
62+
63+
// ===================================================================================================================
64+
65+
trait ForUnsafeSync extends ForDispatch {
66+
implicit def sjrEffectUnsafeSyncOps[F[_], A](fa: F[A])(implicit f: UnsafeSync[F]): UnsafeSyncOps[F, A] =
67+
new UnsafeSyncOps[F, A] {
68+
override protected def F = f
69+
override protected def self = fa
70+
}
71+
}
72+
73+
trait UnsafeSyncOps[F[_], A] extends DispatchOps[F, A] {
74+
override protected def F: UnsafeSync[F]
75+
76+
def runSync() : A = F.runSync(self)
77+
def toJsFn : js.Function0[A] = F.toJsFn(self)
78+
def unless_(cond: => Boolean): F[Unit] = F.unless_(cond)(self)
79+
def when_ (cond: => Boolean): F[Unit] = F.when_(cond)(self)
80+
}
81+
82+
// ===================================================================================================================
83+
84+
trait ForSync extends ForUnsafeSync {
85+
implicit def sjrEffectSyncOps[F[_], A](fa: F[A])(implicit f: Sync[F]): SyncOps[F, A] =
86+
new SyncOps[F, A] {
87+
override protected def F = f
88+
override protected def self = fa
89+
}
90+
}
91+
92+
trait SyncOps[F[_], A] extends UnsafeSyncOps[F, A] {
93+
override protected def F: Sync[F]
94+
95+
def isEmpty: Boolean = F.isEmpty(self)
96+
def reset : F[Unit] = F.reset(self)
97+
}
98+
99+
// ===================================================================================================================
100+
101+
trait ForAsync extends ForDispatch {
102+
implicit def sjrEffectAsyncOps[F[_], A](fa: F[A])(implicit f: Async[F]): AsyncOps[F, A] =
103+
new AsyncOps[F, A] {
104+
override protected def F = f
105+
override protected def self = fa
106+
}
107+
}
108+
109+
trait AsyncOps[F[_], A] extends DispatchOps[F, A] {
110+
override protected def F: Async[F]
111+
112+
def runAsync : Async.Untyped[A] = F.runAsync(self)
113+
def toJsPromise: () => js.Promise[A] = F.toJsPromise(self)
114+
}
115+
116+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package japgolly.scalajs.react.util
2+
3+
package object syntax
4+
extends japgolly.scalajs.react.util.EffectSyntax
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package japgolly.scalajs.react.util
2+
3+
import japgolly.scalajs.react.util.syntax._
4+
5+
trait SyntaxCompilationTest {
6+
7+
def sync[F[_]: Effect.Sync](f: F[Int]) =
8+
for {
9+
i <- f
10+
j <- f
11+
} yield {
12+
f.reset.dispatch()
13+
val k = f.runSync()
14+
i + j + k
15+
}
16+
17+
def async[F[_]: Effect.Async](f: F[Int]) =
18+
for {
19+
i <- f
20+
j <- f
21+
} yield {
22+
val _ = f.toJsPromise
23+
i + j
24+
}
25+
26+
def dispatch[F[_]: Effect.Dispatch](f: F[Int]) =
27+
for {
28+
i <- f
29+
j <- f
30+
} yield {
31+
val _ = f.dispatch()
32+
i + j
33+
}
34+
35+
}

0 commit comments

Comments
 (0)