-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #28 from JD557/add-rio
Update CanvasIO to be backed by a RIO implementation
- Loading branch information
Showing
5 changed files
with
137 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
pure/shared/src/main/scala/eu/joaocosta/minart/pure/RIO.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package eu.joaocosta.minart.pure | ||
|
||
import eu.joaocosta.minart.core._ | ||
|
||
/** | ||
* Representation of an effectful operation, based on Haskell's RIO Monad. | ||
*/ | ||
sealed trait RIO[-R, +A] { | ||
/** Runs this operation */ | ||
def run(resource: R): A | ||
/** Maps the result of this operation. */ | ||
def map[B](f: A => B): RIO[R, B] | ||
/** Combines two operations by applying a function to the result of the first operation. */ | ||
def flatMap[RR <: R, B](f: A => RIO[RR, B]): RIO[RR, B] = RIO.FlatMap[RR, A, B](this, f) | ||
/** Combines two operations by discarding the result of the first operation. */ | ||
def andThen[RR <: R, B](that: RIO[RR, B]): RIO[RR, B] = RIO.FlatMap[RR, A, B](this, _ => that) | ||
/** Combines two operations by discarding the result of the second operation. */ | ||
def andFinally[RR <: R, B](that: RIO[RR, B]): RIO[RR, A] = RIO.FlatMap[RR, A, A](this, x => that.as(x)) | ||
/** Combines two operations by combining their results with the given function. */ | ||
def zipWith[RR <: R, B, C](that: RIO[RR, B])(f: (A, B) => C): RIO[RR, C] = this.flatMap(x => that.map(y => f(x, y))) | ||
/** Combines two operations by combining their results into a tuple. */ | ||
def zip[RR <: R, B](that: RIO[RR, B]): RIO[RR, (A, B)] = this.zipWith(that)((x, y) => x -> y) | ||
/** Changes the result of this operation to another value */ | ||
def as[B](x: B): RIO[R, B] = this.map(_ => x) | ||
/** Transforms the resource required by this operation */ | ||
def contramap[RR](f: RR => R): RIO[RR, A] = RIO.Suspend[RR, A](res => this.run(f(res))) | ||
/** Provides the required resource to this operation */ | ||
def provide(res: R): RIO[Any, A] = this.contramap(_ => res) | ||
/** Changes the result of this operation unit */ | ||
lazy val unit: RIO[R, Unit] = this.as(()) | ||
} | ||
|
||
object RIO { | ||
private final case class Suspend[R, A](thunk: R => A) extends RIO[R, A] { | ||
def run(resource: R): A = thunk(resource) | ||
def map[B](f: A => B): RIO[R, B] = Suspend(thunk.andThen(f)) | ||
} | ||
private final case class FlatMap[R, A, B](io: RIO[R, A], andThen: A => RIO[R, B]) extends RIO[R, B] { | ||
def run(resource: R): B = andThen(io.run(resource)).run(resource) | ||
def map[C](f: B => C): RIO[R, C] = FlatMap[R, B, C](this, x => Suspend(_ => f(x))) | ||
} | ||
|
||
/** An operation that does nothing **/ | ||
val noop: RIO[Any, Unit] = Suspend[Any, Unit](_ => ()) | ||
|
||
/** Lifts a value into a [[RIO]]. */ | ||
def pure[A](x: A): RIO[Any, A] = Suspend[Any, A](_ => x) | ||
|
||
/** Suspends a computation into a [[RIO]]. */ | ||
def suspend[A](x: => A): RIO[Any, A] = Suspend[Any, A](_ => x) | ||
|
||
/** Returns a operation that requires some resource. */ | ||
def access[R, A](f: R => A): RIO[R, A] = Suspend[R, A](f) | ||
|
||
/** Converts an `Iterable[RIO[R, A]]` into a `RIO[R, List[A]]`. */ | ||
def sequence[R, A](it: Iterable[RIO[R, A]]): RIO[R, List[A]] = | ||
access(res => it.map(_.run(res)).toList) | ||
|
||
/** Converts an `Iterable[RIO[R, A]]` into a `RIO[R, Unit]`. */ | ||
def sequence_[R](it: Iterable[RIO[R, Any]]): RIO[R, Unit] = | ||
access(res => it.foreach(_.run(res))) | ||
|
||
/** Converts an `Iterable[A]` into a `RIO[R, List[B]]` by applying an operation to each element. */ | ||
def traverse[R, A, B](it: Iterable[A])(f: A => RIO[R, B]): RIO[R, List[B]] = | ||
sequence(it.map(f)) | ||
|
||
/** Applies an operation to each element of a `Iterable[A]` and discards the result. */ | ||
def foreach[R, A](it: Iterable[A])(f: A => RIO[R, Any]): RIO[R, Unit] = | ||
sequence_(it.map(f)) | ||
} |
8 changes: 8 additions & 0 deletions
8
pure/shared/src/main/scala/eu/joaocosta/minart/pure/package.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package eu.joaocosta.minart | ||
|
||
import eu.joaocosta.minart.core.Canvas | ||
|
||
package object pure { | ||
type CanvasIO[+A] = RIO[Canvas, A] | ||
} | ||
|
52 changes: 52 additions & 0 deletions
52
pure/shared/src/test/scala/eu/joaocosta/minart/pure/RIOSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package eu.joaocosta.minart.pure | ||
|
||
import org.specs2.mutable._ | ||
|
||
import eu.joaocosta.minart.core._ | ||
|
||
class RIOSpec extends Specification { | ||
"A RIO" should { | ||
"store pure results" in { | ||
RIO.pure(1).run(()) === 1 | ||
} | ||
|
||
"suspend computations" in { | ||
var hasRun: Boolean = false | ||
val io = RIO.suspend({ hasRun = true }) | ||
hasRun === false | ||
io.run(()) | ||
hasRun === true | ||
} | ||
|
||
"provide a stack-safe map operation" in { | ||
val io = (1 to 1000).foldLeft[RIO[Any, Int]](RIO.pure(0)) { case (acc, _) => acc.map(_ + 1) } | ||
io.run(()) === 1000 | ||
} | ||
|
||
"provide a stack-safe flatMap operation" in { | ||
val io = (1 to 1000).foldLeft[RIO[Any, Int]](RIO.pure(0)) { case (acc, _) => acc.flatMap(x => RIO.pure(x + 1)) } | ||
io.run(()) === 1000 | ||
} | ||
|
||
"provide zip/zipWith operations" in { | ||
RIO.pure(1).zip(RIO.pure(2)).run(()) === (1, 2) | ||
|
||
RIO.pure(1).zipWith(RIO.pure(2))(_ + _).run(()) === 3 | ||
} | ||
|
||
"provide andThen/andFinally operations" in { | ||
var hasRunAndThen = false | ||
RIO.suspend({ hasRunAndThen = true; 1 }).andThen(RIO.pure(2)).run(()) === 2 | ||
hasRunAndThen === true | ||
|
||
var hasRunAndFinally = false | ||
RIO.pure(1).andFinally(RIO.suspend({ hasRunAndFinally = true; 2 })).run(()) === 1 | ||
hasRunAndFinally === true | ||
} | ||
|
||
"correctly sequence operations" in { | ||
val io = RIO.sequence(List(RIO.pure(1), RIO.pure(2), RIO.pure(3))) | ||
io.run(()) === List(1, 2, 3) | ||
} | ||
} | ||
} |