Skip to content

Commit 0ab4fc3

Browse files
authored
Merge pull request #1004 from rpiaggio/monoid
Derive cats.Monad+Monoid for Effect.Sync.
2 parents 29bcfeb + 99cd383 commit 0ab4fc3

File tree

12 files changed

+285
-60
lines changed

12 files changed

+285
-60
lines changed

callback/src/main/scala-2/japgolly/scalajs/react/callback/CallbackOption.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ object CallbackOption {
3232
def delay[A](a: => A): CallbackOption[A] =
3333
option(Some(a))
3434

35+
def suspend[A](f: => CallbackOption[A]): CallbackOption[A] =
36+
delay(f).flatMap(identityFn)
37+
3538
def option[A](oa: => Option[A]): CallbackOption[A] =
3639
CallbackTo(oa).asCBO
3740

@@ -118,6 +121,14 @@ object CallbackOption {
118121
(implicit cbf: BuildFrom[T[CallbackOption[A]], A, T[A]]): CallbackOption[T[A]] =
119122
traverse(tca)(identityFn)
120123

124+
def traverse_[T[X] <: Iterable[X], A, B](ta: => T[A])(f: A => CallbackOption[B])
125+
(implicit cbf: BuildFrom[T[A], B, T[B]]): CallbackOption[Unit] =
126+
traverse(ta)(f).void
127+
128+
def sequence_[T[X] <: Iterable[X], A](tca: => T[CallbackOption[A]])
129+
(implicit cbf: BuildFrom[T[CallbackOption[A]], A, T[A]]): CallbackOption[Unit] =
130+
traverse_(tca)(identityFn)
131+
121132
/**
122133
* NOTE: Technically a proper, lawful traversal should return `CallbackOption[Option[B]]`.
123134
*/
@@ -273,6 +284,14 @@ final class CallbackOption[+A](val underlyingRepr: CallbackOption.UnderlyingRepr
273284
def when(cond: => Boolean): CallbackOption[A] =
274285
new CallbackOption[A](() => if (cond) cbfn() else None)
275286

287+
/**
288+
* Conditional execution of this callback.
289+
*
290+
* @param cond The condition required to be `true` for this callback to execute.
291+
*/
292+
def when_(cond: => Boolean): CallbackOption[Unit] =
293+
new CallbackOption[Unit](() => if (cond) cbfn().map(_ => ()) else None)
294+
276295
/**
277296
* Conditional execution of this callback.
278297
* Reverse of [[when()]].
@@ -283,6 +302,16 @@ final class CallbackOption[+A](val underlyingRepr: CallbackOption.UnderlyingRepr
283302
@inline def unless(cond: => Boolean): CallbackOption[A] =
284303
when(!cond)
285304

305+
/**
306+
* Conditional execution of this callback.
307+
* Reverse of [[when_()]].
308+
*
309+
* @param cond The condition required to be `false` for this callback to execute.
310+
* @return `Some` result of the callback executed, else `None`.
311+
*/
312+
@inline def unless_(cond: => Boolean): CallbackOption[Unit] =
313+
when_(!cond)
314+
286315
def orElse[AA >: A](tryNext: CallbackOption[AA]): CallbackOption[AA] =
287316
new CallbackOption(() => cbfn().orElse(tryNext.underlyingRepr()))
288317

@@ -300,4 +329,10 @@ final class CallbackOption[+A](val underlyingRepr: CallbackOption.UnderlyingRepr
300329

301330
def handleError[AA >: A](f: Throwable => CallbackOption[AA]): CallbackOption[AA] =
302331
asCallback.handleError(f(_).asCallback).asCBO
332+
333+
/** Wraps this callback in a `try-finally` block and runs the given callback in the `finally` clause, after the
334+
* current callback completes, be it in error or success.
335+
*/
336+
def finallyRun[B](runFinally: CallbackOption[B]): CallbackOption[A] =
337+
asCallback.finallyRun(runFinally.asCallback).asCBO
303338
}

callback/src/main/scala-3/japgolly/scalajs/react/callback/CallbackOption.scala

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ object CallbackOption {
3232
inline def delay[A](inline a: A): CallbackOption[A] =
3333
option(Some(a))
3434

35+
def suspend[A](f: => CallbackOption[A]): CallbackOption[A] =
36+
delay(f).flatMap(identityFn)
37+
3538
inline def option[A](inline oa: Option[A]): CallbackOption[A] =
3639
new CallbackOption(() => oa)
3740

@@ -277,19 +280,37 @@ final class CallbackOption[+A](val underlyingRepr: CallbackOption.UnderlyingRepr
277280
*
278281
* @param cond The condition required to be `true` for this callback to execute.
279282
*/
280-
inline def when(inline cond: Boolean): CallbackOption[A] =
283+
def when(cond: Boolean): CallbackOption[A] =
281284
new CallbackOption[A](() => if (cond) cbfn() else None)
282285

286+
/**
287+
* Conditional execution of this callback.
288+
*
289+
* @param cond The condition required to be `true` for this callback to execute.
290+
*/
291+
def when_(cond: => Boolean): CallbackOption[Unit] =
292+
new CallbackOption[Unit](() => if (cond) cbfn().map(_ => ()) else None)
293+
283294
/**
284295
* Conditional execution of this callback.
285296
* Reverse of [[when()]].
286297
*
287298
* @param cond The condition required to be `false` for this callback to execute.
288299
* @return `Some` result of the callback executed, else `None`.
289300
*/
290-
inline def unless(inline cond: Boolean): CallbackOption[A] =
301+
def unless(cond: Boolean): CallbackOption[A] =
291302
when(!cond)
292303

304+
/**
305+
* Conditional execution of this callback.
306+
* Reverse of [[when_()]].
307+
*
308+
* @param cond The condition required to be `false` for this callback to execute.
309+
* @return `Some` result of the callback executed, else `None`.
310+
*/
311+
inline def unless_(cond: => Boolean): CallbackOption[Unit] =
312+
when_(!cond)
313+
293314
def orElse[AA >: A](tryNext: CallbackOption[AA]): CallbackOption[AA] =
294315
new CallbackOption(() => cbfn().orElse(tryNext.underlyingRepr()))
295316

@@ -307,4 +328,10 @@ final class CallbackOption[+A](val underlyingRepr: CallbackOption.UnderlyingRepr
307328

308329
def handleError[AA >: A](f: Throwable => CallbackOption[AA]): CallbackOption[AA] =
309330
asCallback.handleError(f(_).asCallback).asCBO
331+
332+
/** Wraps this callback in a `try-finally` block and runs the given callback in the `finally` clause, after the
333+
* current callback completes, be it in error or success.
334+
*/
335+
def finallyRun[B](runFinally: CallbackOption[B]): CallbackOption[A] =
336+
asCallback.finallyRun(runFinally.asCallback).asCBO
310337
}

callback/src/main/scala/japgolly/scalajs/react/util/EffectCallback.scala

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ object EffectCallback {
1313
import Effect._
1414

1515
object callback extends Sync.WithDefaultDispatch[CallbackTo] {
16-
1716
override val empty =
1817
Callback.empty
1918

@@ -50,8 +49,8 @@ object EffectCallback {
5049
@inline override def runSync[A](f: => CallbackTo[A]) =
5150
f.runNow()
5251

53-
@inline override def finallyRun[A, B](fa: CallbackTo[A], fb: CallbackTo[B]) =
54-
fa.finallyRun(fb)
52+
@inline override def finallyRun[A, B](fa: => CallbackTo[A], runFinally: => CallbackTo[B]) =
53+
fa.finallyRun(runFinally)
5554

5655
@inline override def toJsFn[A](f: => CallbackTo[A]): js.Function0[A] =
5756
f.toJsFn
@@ -71,42 +70,58 @@ object EffectCallback {
7170
@inline override def sequenceList[A](fas: => List[CallbackTo[A]]) =
7271
CallbackTo.sequence(fas)
7372

74-
@inline override def handleError[A, AA >: A](fa: CallbackTo[A])(f: Throwable => CallbackTo[AA]) =
73+
@inline override def handleError[A, AA >: A](fa: => CallbackTo[A])(f: Throwable => CallbackTo[AA]) =
7574
fa.handleError(f)
7675

7776
@inline override def sequence_[A](fas: => Iterable[CallbackTo[A]]) =
7877
Callback.sequence(fas)
7978

8079
@inline override def when_[A](cond: => Boolean)(fa: => CallbackTo[A]) =
8180
fa.when_(cond)
81+
82+
@inline override def tailrec[A, B](a: A)(f: A => CallbackTo[Either[A,B]]): CallbackTo[B] =
83+
CallbackTo.tailrec(a)(f)
8284
}
8385

8486
// ===================================================================================================================
8587

8688
object callbackOption extends Dispatch[CallbackOption] {
8789

88-
override def delay[A](a: => A): CallbackOption[A] =
90+
@inline override def delay[A](a: => A): CallbackOption[A] =
8991
CallbackOption.delay(a)
9092

91-
override def pure[A](a: A): CallbackOption[A] =
93+
@inline override def pure[A](a: A): CallbackOption[A] =
9294
CallbackOption.pure(a)
9395

94-
override def map[A, B](fa: CallbackOption[A])(f: A => B): CallbackOption[B] =
96+
@inline override def map[A, B](fa: CallbackOption[A])(f: A => B): CallbackOption[B] =
9597
fa map f
9698

97-
override def flatMap[A, B](fa: CallbackOption[A])(f: A => CallbackOption[B]): CallbackOption[B] =
99+
@inline override def flatMap[A, B](fa: CallbackOption[A])(f: A => CallbackOption[B]): CallbackOption[B] =
98100
fa flatMap f
99101

102+
@inline override def tailrec[A, B](a: A)(f: A => CallbackOption[Either[A,B]]) =
103+
CallbackOption.tailrec(a)(f)
104+
105+
override def handleError[A, AA >: A](fa: => CallbackOption[A])(f: Throwable => CallbackOption[AA]) =
106+
fa.handleError(f)
107+
108+
@inline override def finallyRun[A, B](fa: => CallbackOption[A], runFinally: => CallbackOption[B]) =
109+
fa.finallyRun(runFinally)
110+
100111
override def dispatch[A](fa: CallbackOption[A]): Unit =
101112
fa.asCallback.void
102113

103114
override def dispatchFn[A](fa: => CallbackOption[A]): js.Function0[Unit] =
104115
() => {fa.underlyingRepr(); ()}
116+
117+
@inline override def suspend[A](fa: => CallbackOption[A]) =
118+
CallbackOption.suspend(fa)
105119
}
106120

107121
// ===================================================================================================================
108122

109123
object asyncCallback extends Async[AsyncCallback] with Dispatch.WithDefaults[AsyncCallback] {
124+
110125
@inline override def delay[A](a: => A) =
111126
AsyncCallback.delay(a)
112127

@@ -119,8 +134,11 @@ object EffectCallback {
119134
@inline override def flatMap[A, B](fa: AsyncCallback[A])(f: A => AsyncCallback[B]) =
120135
fa.flatMap(f)
121136

122-
@inline override def finallyRun[A, B](fa: AsyncCallback[A], fb: AsyncCallback[B]) =
123-
fa.finallyRun(fb)
137+
@inline override def finallyRun[A, B](fa: => AsyncCallback[A], runFinally: => AsyncCallback[B]) =
138+
fa.finallyRun(runFinally)
139+
140+
override def tailrec[A, B](a: A)(f: A => AsyncCallback[Either[A,B]]): AsyncCallback[B] =
141+
AsyncCallback.tailrec(a)(f)
124142

125143
override def async[A](fa: Async.Untyped[A]): AsyncCallback[A] =
126144
AsyncCallback[A](f => CallbackTo.fromJsFn(fa(f(_).toJsFn)))
@@ -142,5 +160,11 @@ object EffectCallback {
142160

143161
@inline override def dispatch[A](fa: AsyncCallback[A]): Unit =
144162
fa.runNow()
163+
164+
@inline override def handleError[A, AA >: A](fa: => AsyncCallback[A])(f: Throwable => AsyncCallback[AA]) =
165+
fa.handleError(f)
166+
167+
@inline override def suspend[A](fa: => AsyncCallback[A]) =
168+
AsyncCallback.suspend(fa)
145169
}
146170
}

coreExtCats/src/main/scala/japgolly/scalajs/react/ReactCats.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package japgolly.scalajs.react
33
import cats.arrow.Profunctor
44
import cats.data.Ior
55
import cats.kernel.Eq
6+
import cats.{MonadThrow, Monoid}
67
import japgolly.scalajs.react.internal.CoreGeneral.Key
8+
import japgolly.scalajs.react.util.Effect
79
import scala.annotation.nowarn
810

911
object ReactCats extends ReactCats
@@ -23,4 +25,10 @@ trait ReactCats {
2325

2426
@inline final implicit def reactCatsProfunctorRefFull[F[_], X]: Profunctor[Ref.FullF[F, *, X, *]] =
2527
X.reactCatsProfunctorRefFull
28+
29+
@inline final implicit def reactCatsSyncEffectMonadThrow[F[_]: Effect]: MonadThrow[F] =
30+
X.reactCatsSyncEffectMonadThrow
31+
32+
@inline final implicit def reactCatsSyncEffectMonoid[F[_]: Effect, A: Monoid]: Monoid[F[A]] =
33+
X.reactCatsSyncEffectMonoid
2634
}

coreExtCats/src/main/scala/japgolly/scalajs/react/internal/ReactCats.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package japgolly.scalajs.react.internal
33
import cats._
44
import cats.arrow.{Profunctor => CatsProfunctor}
55
import cats.data.Ior
6+
import japgolly.scalajs.react.util.Effect
67
import japgolly.scalajs.react.{ReactExtensions, Ref, Reusability}
78

89
object ReactCats {
@@ -32,4 +33,25 @@ object ReactCats {
3233
override def rmap[A, B, C](f: Ref.FullF[F, A, X, B])(m: B => C) = f.map(m)
3334
override def dimap[A, B, C, D](r: Ref.FullF[F, A, X, B])(f: C => A)(g: B => D) = r.contramap(f).map(g)
3435
}
36+
37+
implicit def reactCatsSyncEffectMonadThrow[F[_]](implicit F: Effect[F]): MonadThrow[F] =
38+
new MonadThrow[F] {
39+
override def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] =
40+
F.flatMap(fa)(f)
41+
42+
override def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] =
43+
F.tailrec(a)(f)
44+
45+
override def pure[A](a: A): F[A] =
46+
F.pure(a)
47+
48+
override def raiseError[A](e: Throwable): F[A] =
49+
F.throwException(e)
50+
51+
override def handleErrorWith[A](fa: F[A])(f: Throwable => F[A]): F[A] =
52+
F.handleError(fa)(f)
53+
}
54+
55+
implicit def reactCatsSyncEffectMonoid[F[_]: Effect, A: Monoid]: Monoid[F[A]] =
56+
Applicative.monoid[F, A]
3557
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package japgolly.scalajs.react
2+
3+
import cats._
4+
import japgolly.scalajs.react.ReactCats._
5+
import japgolly.scalajs.react.util._
6+
7+
object ReactCatsCompilationTest {
8+
9+
def monadThrowA [F[_]](implicit F: Effect.Async[F] ): MonadThrow[F] = implicitly
10+
def monadThrowAS[F[_]](implicit F: Effect.Async[F] with Effect.Sync[F]): MonadThrow[F] = implicitly
11+
def monadThrowS [F[_]](implicit F: Effect.Sync[F] ): MonadThrow[F] = implicitly
12+
def monadThrowSA[F[_]](implicit F: Effect.Sync[F] with Effect.Async[F]): MonadThrow[F] = implicitly
13+
14+
def monoidA [F[_], A](implicit F: Effect.Async[F] , A: Monoid[A]): Monoid[F[A]] = implicitly
15+
def monoidAS[F[_], A](implicit F: Effect.Async[F] with Effect.Sync[F], A: Monoid[A]): Monoid[F[A]] = implicitly
16+
def monoidS [F[_], A](implicit F: Effect.Sync[F] , A: Monoid[A]): Monoid[F[A]] = implicitly
17+
def monoidSA[F[_], A](implicit F: Effect.Sync[F] with Effect.Async[F], A: Monoid[A]): Monoid[F[A]] = implicitly
18+
}

coreExtCatsEffect/src/main/scala/japgolly/scalajs/react/util/EffectCatsEffect.scala

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package japgolly.scalajs.react.util
22

3+
import cats.Monad
34
import cats.effect.unsafe.IORuntime
45
import cats.effect.{IO, SyncIO}
56
import japgolly.scalajs.react.ReactCatsEffect
6-
import scala.util.Try
7+
import japgolly.scalajs.react.util.Util.identityFn
8+
import scala.util.{Success, Try}
79

810
abstract class EffectFallbacks2 extends EffectFallbacks3 {
911
implicit def syncIO: Effect.Sync [SyncIO] = EffectCatsEffect.syncIO
@@ -14,6 +16,7 @@ object EffectCatsEffect {
1416
import Effect._
1517

1618
implicit object syncIO extends Sync.WithDefaults[SyncIO] {
19+
private[this] final val M = Monad[SyncIO]
1720

1821
override val empty =
1922
SyncIO.unit
@@ -35,14 +38,15 @@ object EffectCatsEffect {
3538

3639
@inline override def runSync[A](f: => SyncIO[A]) =
3740
f.unsafeRunSync()
41+
42+
override def tailrec[A, B](a: A)(f: A => SyncIO[Either[A, B]]) =
43+
M.tailRecM(a)(f)
3844
}
3945

4046
// ===================================================================================================================
4147

42-
implicit lazy val io: AsyncIO =
43-
new AsyncIO(ReactCatsEffect.runtimeFn)
44-
45-
class AsyncIO(runtime: () => IORuntime) extends Async.WithDefaults[IO] {
48+
class EffectIO extends Effect[IO] {
49+
protected final val M = Monad[IO]
4650

4751
@inline override def delay[A](a: => A) =
4852
IO.delay(a)
@@ -56,11 +60,28 @@ object EffectCatsEffect {
5660
@inline override def flatMap[A, B](fa: IO[A])(f: A => IO[B]) =
5761
fa.flatMap(f)
5862

59-
override def finallyRun[A, B](fa: IO[A], fb: IO[B]) =
63+
override def finallyRun[A, B](fa: => IO[A], fb: => IO[B]) =
6064
fa.attempt.flatMap(ta =>
6165
fb.attempt.flatMap(tb =>
6266
IO.fromEither(if (ta.isRight && tb.isLeft) Left(tb.left.getOrElse(null)) else ta)))
6367

68+
override def tailrec[A, B](a: A)(f: A => IO[Either[A, B]]) =
69+
M.tailRecM(a)(f)
70+
71+
override def handleError[A, AA >: A](fa: => IO[A])(f: Throwable => IO[AA]) =
72+
fa.handleErrorWith(f)
73+
74+
override def suspend[A](fa: => IO[A]) =
75+
IO(fa).flatMap(identityFn)
76+
}
77+
78+
// ===================================================================================================================
79+
80+
implicit lazy val io: AsyncIO =
81+
new AsyncIO(ReactCatsEffect.runtimeFn)
82+
83+
class AsyncIO(runtime: () => IORuntime) extends EffectIO with Async.WithDefaults[IO] {
84+
6485
override def async[A](fa: Async.Untyped[A]): IO[A] =
6586
IO.async(f => IO.delay {
6687
fa(ta => () => f(ta.toEither))()
@@ -82,5 +103,5 @@ object EffectCatsEffect {
82103
}
83104

84105
private lazy val tryUnit: Try[Unit] =
85-
Try(())
106+
Success(())
86107
}

0 commit comments

Comments
 (0)