Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cats Effect: STM & TRef #791 #796

Merged
merged 12 commits into from
Apr 30, 2019
225 changes: 225 additions & 0 deletions interop-cats/jvm/src/main/scala/scalaz/zio/interop/stm/STM.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/*
* Copyright 2017-2019 John A. De Goes and the ZIO Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package scalaz.zio.interop.stm

import cats.effect.Async
import scalaz.zio.stm.{ STM => ZSTM }
import scalaz.zio.Runtime

import scala.util.Try

/**
* See [[scalaz.zio.stm.STM]]
*/
final class STM[F[+ _], +A] private[stm] (private[stm] val underlying: ZSTM[Throwable, A]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks super clean. I think you can extend AnyVal here, too, now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to, but scala compiler has issue with nested AnyVal classes, actually it does not support this feature yet. I didn't find any workarounds at this point. Anyways I think AnyVal has a bunch of restrictions that could make all workarounds meaningless (e.g. passing underlying ZSTM as a trait or other type leads to allocation).

self =>

/**
* See `<*>` [[scalaz.zio.stm.STM]] `<*>`
*/
final def <*>[B](that: => STM[F, B]): STM[F, (A, B)] =
self zip that

/**
* See [[scalaz.zio.stm.STM]] `<*`
*/
final def <*[B](that: => STM[F, B]): STM[F, A] =
self zipLeft that

/**
* See [[scalaz.zio.stm.STM]] `*>`
*/
final def *>[B](that: => STM[F, B]): STM[F, B] =
self zipRight that

/**
* See [[scalaz.zio.stm.STM]] `>>=`
*/
final def >>=[B](f: A => STM[F, B]): STM[F, B] =
self flatMap f

/**
* See [[scalaz.zio.stm.STM#collect]]
*/
final def collect[B](pf: PartialFunction[A, B]): STM[F, B] = new STM(underlying.collect(pf))

/**
* See [[scalaz.zio.stm.STM#commit]]
*/
final def commit(implicit R: Runtime[Any], A: Async[F]): F[A] = STM.atomically(self)

/**
* See [[scalaz.zio.stm.STM#const]]
*/
final def const[B](b: => B): STM[F, B] = self map (_ => b)

/**
* See [[scalaz.zio.stm.STM#either]]
*/
final def either: STM[F, Either[Throwable, A]] = new STM(underlying.either)

/**
* See [[scalaz.zio.stm.STM#filter]]
*/
final def filter(f: A => Boolean): STM[F, A] =
collect {
case a if f(a) => a
}

/**
* See [[scalaz.zio.stm.STM#flatMap]]
*/
final def flatMap[B](f: A => STM[F, B]): STM[F, B] = new STM(underlying.flatMap(f.andThen(_.underlying)))

/**
* See [[scalaz.zio.stm.STM#flatten]]
*/
final def flatten[B](implicit ev: A <:< STM[F, B]): STM[F, B] =
self flatMap ev

/**
* See [[scalaz.zio.stm.STM#fold]]
*/
final def fold[B](f: Throwable => B, g: A => B): STM[F, B] = new STM(underlying.fold(f, g))

/**
* See [[scalaz.zio.stm.STM#foldM]]
*/
final def foldM[B](f: Throwable => STM[F, B], g: A => STM[F, B]): STM[F, B] =
new STM(underlying.foldM(f.andThen(_.underlying), g.andThen(_.underlying)))

/**
* See [[scalaz.zio.stm.STM#map]]
*/
final def map[B](f: A => B): STM[F, B] = new STM(underlying.map(f))

/**
* See [[scalaz.zio.stm.STM#mapError]]
*/
final def mapError[E1 <: Throwable](f: Throwable => E1): STM[F, A] = new STM(underlying.mapError(f))

/**
* See [[scalaz.zio.stm.STM#option]]
*/
final def option: STM[F, Option[A]] =
fold[Option[A]](_ => None, Some(_))

/**
* See [[scalaz.zio.stm.STM#orElse]]
*/
final def orElse[A1 >: A](that: => STM[F, A1]): STM[F, A1] = new STM(underlying.orElse(that.underlying))

/**
* See [[scalaz.zio.stm.STM#orElseEither]]
*/
final def orElseEither[B](that: => STM[F, B]): STM[F, Either[A, B]] =
(self map (Left[A, B](_))) orElse (that map (Right[A, B](_)))

/**
* See scalaz.zio.stm.STM.unit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Scaladoc link?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this because in this case sbt doc command fails with err message like Could not find any member to link for "scalaz.zio.stm.STM.unit". I suspect this is because it sees unit in the STM class and also in companion object, and fails to resolve which unit to link.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries!

*/
final def unit: STM[F, Unit] = const(())

/**
* See scalaz.zio.stm.STM.unit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Scaladoc link?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same problem here.

*/
final def void: STM[F, Unit] = unit

/**
* Same as [[filter]]
*/
final def withFilter(f: A => Boolean): STM[F, A] = filter(f)

/**
* See [[scalaz.zio.stm.STM#zip]]
*/
final def zip[B](that: => STM[F, B]): STM[F, (A, B)] =
(self zipWith that)((a, b) => a -> b)

/**
* See [[scalaz.zio.stm.STM#zipLeft]]
*/
final def zipLeft[B](that: => STM[F, B]): STM[F, A] =
(self zip that) map (_._1)

/**
* See [[scalaz.zio.stm.STM#zipRight]]
*/
final def zipRight[B](that: => STM[F, B]): STM[F, B] =
(self zip that) map (_._2)

/**
* See [[scalaz.zio.stm.STM#zipWith]]
*/
final def zipWith[B, C](that: => STM[F, B])(f: (A, B) => C): STM[F, C] =
self flatMap (a => that map (b => f(a, b)))

/**
* Switch from effect F to effect G using transformation `f`.
*/
final def mapK[G[+ _]]: STM[G, A] = new STM(underlying)
}

object STM {

final def succeed[F[+ _], A](a: A): STM[F, A] = new STM(ZSTM.succeed(a))

final def atomically[F[+ _], A](stm: STM[F, A])(implicit R: Runtime[Any], A: Async[F]): F[A] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful! Just this single method needs the implicit.

A.async { cb =>
R.unsafeRunAsync(ZSTM.atomically(stm.underlying)) { exit =>
cb(exit.toEither)
}
}

final def check[F[+ _]](p: Boolean): STM[F, Unit] =
if (p) STM.unit else retry

final def collectAll[F[+ _], A](
i: Iterable[STM[F, A]]
): STM[F, List[A]] =
new STM(ZSTM.collectAll(i.map(_.underlying)))

final def die[F[+ _]](t: Throwable): STM[F, Nothing] =
succeedLazy(throw t)

final def dieMessage[F[+ _]](m: String): STM[F, Nothing] =
die(new RuntimeException(m))

final def fail[F[+ _]](e: Throwable): STM[F, Nothing] =
new STM(ZSTM.fail(e))

final def foreach[F[+ _], A, B](
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need an implicit.

as: Iterable[A]
)(f: A => STM[F, B]): STM[F, List[B]] =
collectAll(as.map(f))

final def fromEither[F[+ _], A](e: Either[Throwable, A]): STM[F, A] =
new STM(ZSTM.fromEither(e))

final def fromTry[F[+ _], A](a: => Try[A]): STM[F, A] =
new STM(ZSTM.fromTry(a))

final def partial[F[+ _], A](a: => A): STM[F, A] =
fromTry(Try(a))

final def retry[F[+ _]]: STM[F, Nothing] = new STM(ZSTM.retry)

final def succeedLazy[F[+ _], A](a: => A): STM[F, A] =
new STM(ZSTM.succeedLazy(a))

final def unit[F[+ _]]: STM[F, Unit] = succeed(())
}
72 changes: 72 additions & 0 deletions interop-cats/jvm/src/main/scala/scalaz/zio/interop/stm/TRef.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2017-2019 John A. De Goes and the ZIO Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package scalaz.zio.interop.stm

import cats.effect.Async
import scalaz.zio.Runtime
import scalaz.zio.stm.{ TRef => ZTRef }

class TRef[F[+ _], A] private (val underlying: ZTRef[A]) extends AnyVal {
self =>

/**
* See [[scalaz.zio.stm.TRef#get]]
*/
final def get: STM[F, A] = new STM(underlying.get)

/**
* See [[scalaz.zio.stm.TRef#set]]
*/
final def set(newValue: A): STM[F, Unit] = new STM(underlying.set(newValue))

override final def toString = underlying.toString

/**
* See [[scalaz.zio.stm.TRef#update]]
*/
final def update(f: A => A): STM[F, A] = new STM(underlying.update(f))

/**
* See [[scalaz.zio.stm.TRef#updateSome]]
*/
final def updateSome(f: PartialFunction[A, A]): STM[F, A] = new STM(underlying.updateSome(f))

/**
* See [[scalaz.zio.stm.TRef#modify]]
*/
final def modify[B](f: A => (B, A)): STM[F, B] = new STM(underlying.modify(f))

/**
* See [[scalaz.zio.stm.TRef#modifySome]]
*/
final def modifySome[B](default: B)(f: PartialFunction[A, (B, A)]): STM[F, B] =
new STM(underlying.modifySome(default)(f))

/**
* Switch from effect F to effect G using transformation `f`.
*/
def mapK[G[+ _]]: TRef[G, A] = new TRef(underlying)
}

object TRef {

final def make[F[+ _], A](a: => A): STM[F, TRef[F, A]] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all looks good to me! I'd just sort the methods alphabetically, then it looks good to merge! 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

new STM(ZTRef.make(a).map(new TRef(_)))

final def makeCommit[F[+ _], A](a: => A)(implicit R: Runtime[Any], A: Async[F]): F[TRef[F, A]] =
STM.atomically(make(a))
}