Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ val CatsMtlVersion = "1.2.1"
val DisciplineVersion = "1.3.1"
val Specs2Version = "4.13.3"

lazy val root = tlCrossRootProject.aggregate(kernel, laws, std, core)
lazy val root = tlCrossRootProject.aggregate(kernel, laws, core)

lazy val kernel = crossProject(JVMPlatform, JSPlatform)
.crossType(CrossType.Pure)
Expand All @@ -33,6 +33,7 @@ lazy val kernel = crossProject(JVMPlatform, JSPlatform)
name := "oxidized-kernel",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-core" % CatsVersion,
"org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion,
"org.typelevel" %%% "cats-mtl" % CatsMtlVersion
)
)
Expand All @@ -49,21 +50,10 @@ lazy val laws = crossProject(JVMPlatform, JSPlatform)
)
)

lazy val std = crossProject(JVMPlatform, JSPlatform)
.crossType(CrossType.Pure)
.in(file("std"))
.dependsOn(kernel)
.settings(
name := "oxidized-std",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect-kernel" % CatsEffectVersion
)
)

lazy val core = crossProject(JVMPlatform, JSPlatform)
.crossType(CrossType.Pure)
.in(file("core"))
.dependsOn(std, laws % Test)
.dependsOn(laws % Test)
.settings(
name := "oxidized",
libraryDependencies ++= Seq(
Expand Down
198 changes: 169 additions & 29 deletions kernel/src/main/scala/oxidized/ConcurrentStateful.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,51 +16,191 @@

package oxidized

import cats.Functor
import cats.Monad
import cats.data.State
import cats.effect.kernel.Ref
import cats.effect.kernel.Unique
import cats.mtl.MonadPartialOrder
import cats.mtl.Stateful
import cats.syntax.all._

trait ConcurrentStateful[F[_], S] extends Serializable {
def functor: Functor[F]
def monad: Monad[F]
def unique: Unique[F]

def get: F[S]

def set(s: S): F[Unit]
def modify(f: S => S): F[Unit]
def inspect[A](f: S => A): F[A] = functor.map(get)(f)
// TODO To be useful will probably need more Ref-like methods

def inspect[A](f: S => A): F[A] = monad.map(get)(f)

/**
* Updates the current value using `f` and returns the previous value.
*
* In case of retries caused by concurrent modifications, the returned value will be the last
* one before a successful update.
*/
def getAndUpdate(f: S => S): F[S] = modify { s => (f(s), s) }

/**
* Replaces the current value with `a`, returning the previous value.
*/
def getAndSet(s: S): F[S] = getAndUpdate(_ => s)

/**
* Updates the current value using `f`, and returns the updated value.
*/
def updateAndGet(f: S => S): F[S] =
modify { s =>
val newS = f(s)
(newS, newS)
}

/**
* Attempts to modify the current value once, returning `false` if another concurrent
* modification completes between the time the variable is read and the time it is set.
*/
def tryUpdate(f: S => S): F[Boolean] =
monad.map(tryModify(s => (f(s), ())))(_.isDefined)

/**
* Like `tryUpdate` but allows the update function to return an output value of type `B`. The
* returned action completes with `None` if the value is not updated successfully and
* `Some(b)` otherwise.
*/
def tryModify[A](f: S => (S, A)): F[Option[A]]

/**
* Modifies the current value using the supplied update function. If another modification
* occurs between the time the current value is read and subsequently updated, the
* modification is retried using the new value. Hence, `f` may be invoked multiple times.
*
* Satisfies: `r.update(_ => a) == r.set(a)`
*/
def update(f: S => S): F[Unit] = modify(s => (f(s), ()))

/**
* Like `tryModify` but does not complete until the update has been successfully made.
*/
def modify[A](f: S => (S, A)): F[A]

/**
* Update the value of this ref with a state computation.
*
* The current value of this ref is used as the initial state and the computed output state is
* stored in this ref after computation completes. If a concurrent modification occurs, `None`
* is returned.
*/
def tryModifyState[A](state: State[S, A]): F[Option[A]] =
tryModify(state.run(_).value)

/**
* Like [[tryModifyState]] but retries the modification until successful.
*/
def modifyState[A](state: State[S, A]): F[A] =
modify(state.run(_).value)

}

object ConcurrentStateful {
def apply[F[_], S](implicit stateful: ConcurrentStateful[F, S]): ConcurrentStateful[F, S] =
stateful
object ConcurrentStateful extends LowPriorityConcurrentStatefulInstances {
@inline def apply[F[_], S](implicit cs: ConcurrentStateful[F, S]): ConcurrentStateful[F, S] =
cs

/**
* Creates a `ConcurrentStateful` backed by a `Ref`
*/
def ref[F[_]: Monad: Unique, S](s: S)(implicit mk: Ref.Make[F]): F[ConcurrentStateful[F, S]] =
mk.refOf(s).map(fromRef(_))

def fromRef[F[_], S](
ref: Ref[F, S])(implicit F: Monad[F], U: Unique[F]): ConcurrentStateful[F, S] =
new ConcurrentStateful[F, S] {
def monad = F
def unique = U
def get = ref.get
def set(s: S) = ref.set(s)
def tryModify[A](f: S => (S, A)) = ref.tryModify(f)
def modify[A](f: S => (S, A)) = ref.modify(f)
}

@inline def get[F[_], S](implicit cs: ConcurrentStateful[F, S]): F[S] =
cs.get

@inline def set[F[_], S](newState: S)(implicit cs: ConcurrentStateful[F, S]): F[Unit] =
cs.set(newState)

@inline def inspect[F[_], S, A](f: S => A)(implicit cs: ConcurrentStateful[F, S]): F[A] =
cs.inspect(f)

@inline def getAndUpdate[F[_], S](f: S => S)(implicit cs: ConcurrentStateful[F, S]): F[S] =
cs.getAndUpdate(f)

@inline def getAndSet[F[_], S](s: S)(implicit cs: ConcurrentStateful[F, S]): F[S] =
cs.getAndSet(s)

@inline def updateAndGet[F[_], S](f: S => S)(implicit cs: ConcurrentStateful[F, S]): F[S] =
cs.updateAndGet(f)

def get[F[_], S](implicit ev: ConcurrentStateful[F, S]): F[S] =
ev.get
@inline def tryUpdate[F[_], S](f: S => S)(implicit cs: ConcurrentStateful[F, S]): F[Boolean] =
cs.tryUpdate(f)

def set[F[_], S](newState: S)(implicit ev: ConcurrentStateful[F, S]): F[Unit] =
ev.set(newState)
@inline def tryModify[F[_], S, A](f: S => (S, A))(
implicit cs: ConcurrentStateful[F, S]): F[Option[A]] =
cs.tryModify(f)

def setF[F[_]]: setFPartiallyApplied[F] = new setFPartiallyApplied[F]
@inline def update[F[_], S](f: S => S)(implicit cs: ConcurrentStateful[F, S]): F[Unit] =
cs.update(f)

final private[oxidized] class setFPartiallyApplied[F[_]](val dummy: Boolean = false)
extends AnyVal {
@inline def apply[E, A](e: E)(implicit state: ConcurrentStateful[F, E]): F[Unit] =
state.set(e)
}
@inline def modify[F[_], S, A](f: S => (S, A))(implicit cs: ConcurrentStateful[F, S]): F[A] =
cs.modify(f)

def modify[F[_], S](f: S => S)(implicit state: ConcurrentStateful[F, S]): F[Unit] =
state.modify(f)
@inline def tryModifyState[F[_], S, A](state: State[S, A])(
implicit cs: ConcurrentStateful[F, S]): F[Option[A]] =
cs.tryModifyState(state)

def inspect[F[_], S, A](f: S => A)(implicit state: ConcurrentStateful[F, S]): F[A] =
state.inspect(f)
@inline def modifyState[F[_], S, A](state: State[S, A])(
implicit cs: ConcurrentStateful[F, S]): F[A] =
cs.modifyState(state)

implicit def oxidizedConcurrentStatefulForStateful[F[_], S](
implicit F: Stateful[F, S]): ConcurrentStateful[F, S] =
implicit def concurrentStatefulForStateful[F[_], S](
implicit stateful: Stateful[F, S],
U: Unique[F]): ConcurrentStateful[F, S] =
new ConcurrentStateful[F, S] {
override def functor: Functor[F] = F.monad
def monad = stateful.monad
def unique = U

def get = stateful.get

def set(s: S) = stateful.set(s)

def modify[A](f: S => (S, A)) =
monad.flatMap(get) { s =>
val (s1, a) = f(s)
monad.as(set(s1), a)
}

def tryModify[A](f: S => (S, A)) =
monad.map(modify(f))(Some(_))
}
}

private[oxidized] trait LowPriorityConcurrentStatefulInstances {

override def get: F[S] = F.get
override def set(s: S): F[Unit] = F.set(s)
override def modify(f: S => S): F[Unit] = F.modify(f)
override def inspect[A](f: S => A): F[A] = F.inspect(f)
implicit def concurrentStatefulForPartialOrder[F[_], G[_], S](
implicit liftF: MonadPartialOrder[
F,
G
], // NB don't make this the *second* parameter; it won't infer
cs: ConcurrentStateful[F, S]): ConcurrentStateful[G, S] =
new ConcurrentStateful[G, S] {
def monad = liftF.monadG
def unique = new Unique[G] {
def applicative = monad
def unique = liftF(cs.unique.unique)
}
def get = liftF(cs.get)
def set(s: S) = liftF(cs.set(s))
def tryModify[A](f: S => (S, A)) = liftF(cs.tryModify(f))
def modify[A](f: S => (S, A)) = liftF(cs.modify(f))
}
}
26 changes: 18 additions & 8 deletions laws/src/main/scala/oxidized/laws/ConcurrentStatefulLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,32 @@
* limitations under the License.
*/

package oxidized.laws
package oxidized
package laws

import oxidized.ConcurrentStateful
import cats.Functor
import cats.Monad
import cats.effect.kernel.Unique
import cats.kernel.laws.IsEq
import cats.syntax.all._

trait ConcurrentStatefulLaws[F[_], S] {
implicit def stateInstance: ConcurrentStateful[F, S]
implicit def functor: Functor[F] = stateInstance.functor
implicit def concurrentStateful: ConcurrentStateful[F, S]
implicit def monad: Monad[F] = concurrentStateful.monad
implicit def unique: Unique[F] = concurrentStateful.unique

// TODO
def setThenSetOverwritesFirst: F[Boolean] = for {
u1 <- unique.unique
u2 <- unique.unique
_ <- concurrentStateful.set(u1)
_ <- concurrentStateful.set(u2)
got <- concurrentStateful.get
} yield got != u1
}

object ConcurrentStatefulLaws {
def apply[F[_], S](
implicit instance0: ConcurrentStateful[F, S]): ConcurrentStatefulLaws[F, S] =
implicit cs: ConcurrentStateful[F, S]): ConcurrentStatefulLaws[F, S] =
new ConcurrentStatefulLaws[F, S] {
override val stateInstance: ConcurrentStateful[F, S] = instance0
val concurrentStateful = cs
}
}
35 changes: 0 additions & 35 deletions std/src/main/scala/oxidized/instances/ref.scala

This file was deleted.