This repository has been archived by the owner on Jan 26, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
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 MakeNowJust-Labo/lazy
- Loading branch information
Showing
13 changed files
with
362 additions
and
3 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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# lite-crazy | ||
|
||
> A lazy-evaluated value cell and time travelling state monads implementations. | ||
[![Maven Central](https://img.shields.io/maven-central/v/codes.quine.labo/lite-crazy_2.13?logo=scala&style=for-the-badge)](https://search.maven.org/artifact/codes.quine.labo/lite-crazy_2.13) | ||
|
||
## Install | ||
|
||
Insert the following to your `build.sbt`. | ||
|
||
```sbt | ||
libraryDependencies += "codes.quine.labo" %% "lite-crazy" % "0.3.0" | ||
``` | ||
|
||
## Reference | ||
|
||
- <https://lukepalmer.wordpress.com/2008/08/10/mindfuck-the-reverse-state-monad/> | ||
- <https://blog.csongor.co.uk/time-travel-in-haskell-for-dummies/> |
38 changes: 38 additions & 0 deletions
38
modules/lite-crazy/shared/src/main/scala/codes/quine/labo/lite/crazy/Lazy.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,38 @@ | ||
package codes.quine.labo.lite.crazy | ||
|
||
/** Lazy is a lazy-evaluated value cell. */ | ||
final class Lazy[A] private (private[this] var thunk: () => A) { | ||
|
||
private[this] var cache: Option[A] = None | ||
|
||
/** Returns an underlying value. */ | ||
def value: A = cache match { | ||
case Some(x) => x | ||
case None => | ||
synchronized { | ||
val x = thunk() | ||
thunk = null | ||
cache = Some(x) | ||
x | ||
} | ||
} | ||
|
||
/** Applies an underlying value to the given mapping, and returns a new lazy cell holds this result. */ | ||
def map[B](f: A => B): Lazy[B] = Lazy(f(value)) | ||
|
||
/** Applies an underlying value to the given mapping, and returns a new lazy cell holds flattened result. */ | ||
def flatMap[B](f: A => Lazy[B]): Lazy[B] = Lazy(f(value).value) | ||
} | ||
|
||
object Lazy { | ||
|
||
/** Returns a lazy-evaluated value cell with the given value. */ | ||
def apply[A](x: => A): Lazy[A] = new Lazy(() => x) | ||
|
||
/** Returns an lazy-evaluated value cell getting from fixpoint function. */ | ||
def fix[A](f: Lazy[A] => A): Lazy[A] = { | ||
var lx: Lazy[A] = null | ||
lx = Lazy(f(lx)) | ||
lx | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
modules/lite-crazy/shared/src/main/scala/codes/quine/labo/lite/crazy/RState.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,42 @@ | ||
package codes.quine.labo.lite.crazy | ||
|
||
/** RState is reversed state monad implementation. */ | ||
final case class RState[S, A](run: Lazy[S] => (Lazy[S], Lazy[A])) { | ||
|
||
/** Applies a result value to the given mapping, and returns a new reversed state monad holds this result. */ | ||
def map[B](f: Lazy[A] => Lazy[B]): RState[S, B] = flatMap(lx => RState.pure(f(lx))) | ||
|
||
/** Applies a result value to the given mapping, and returns a new reversed state monad holds flattened result. | ||
* Hence the mapping requires a lazy cell as its argument, we can write an action depends on future state. | ||
*/ | ||
def flatMap[B](f: Lazy[A] => RState[S, B]): RState[S, B] = | ||
RState[S, B] { ls0 => | ||
type Fix = (Lazy[(Lazy[S], Lazy[A])], Lazy[(Lazy[S], Lazy[B])], Lazy[S], Lazy[S], Lazy[A], Lazy[B]) | ||
val lfix = Lazy.fix[Fix] { lfix => | ||
val ls2x = Lazy(run(lfix.value._3)) | ||
val ls1y = Lazy(f(lfix.value._5).run(ls0)) | ||
val ls1 = Lazy(ls1y.value._1.value) | ||
val ls2 = Lazy(ls2x.value._1.value) | ||
val lx = Lazy(ls2x.value._2.value) | ||
val ly = Lazy(ls1y.value._2.value) | ||
(ls2x, ls1y, ls1, ls2, lx, ly) | ||
} | ||
val (_, _, _, ls2, _, ly) = lfix.value | ||
(ls2, ly) | ||
} | ||
} | ||
|
||
object RState { | ||
|
||
/** Returns a reversed state monad holds the specified value as its result value. */ | ||
def pure[S, A](lx: Lazy[A]): RState[S, A] = RState(ls => (ls, lx)) | ||
|
||
/** Returns a reversed state monad holds a future state as its result value. */ | ||
def get[S]: RState[S, S] = RState(ls => (ls, ls)) | ||
|
||
/** Returns a reversed state monad for putting the specified value to its state. */ | ||
def put[S](ls: Lazy[S]): RState[S, Unit] = RState(_ => (ls, Lazy(()))) | ||
|
||
/** Returns a reversed state monad for modifying its state by the given mapping. */ | ||
def modify[S](f: Lazy[S] => Lazy[S]): RState[S, Unit] = RState(ls => (f(ls), Lazy(()))) | ||
} |
64 changes: 64 additions & 0 deletions
64
modules/lite-crazy/shared/src/main/scala/codes/quine/labo/lite/crazy/Tardis.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,64 @@ | ||
package codes.quine.labo.lite.crazy | ||
|
||
/** Tardis a state monad implementation holds both of backward and forward states. */ | ||
final case class Tardis[BW, FW, A](run: (Lazy[BW], Lazy[FW]) => (Lazy[BW], Lazy[FW], Lazy[A])) { | ||
|
||
/** Applies a result value to the given mapping, and returns a new tardis monad holds this result. */ | ||
def map[B](f: Lazy[A] => Lazy[B]): Tardis[BW, FW, B] = flatMap(lx => Tardis.pure(f(lx))) | ||
|
||
/** Applies a result value to the given mapping, and returns a new tardis monad holds flattened result. | ||
* Hence the mapping requires lazy cells as its arguments, we can write an action depends on both of future and past state. | ||
*/ | ||
def flatMap[B](f: Lazy[A] => Tardis[BW, FW, B]): Tardis[BW, FW, B] = | ||
Tardis { case (lb0, lf0) => | ||
type Fix = ( | ||
Lazy[(Lazy[BW], Lazy[FW], Lazy[A])], | ||
Lazy[(Lazy[BW], Lazy[FW], Lazy[B])], | ||
Lazy[BW], | ||
Lazy[BW], | ||
Lazy[FW], | ||
Lazy[FW], | ||
Lazy[A], | ||
Lazy[B] | ||
) | ||
val lfix = Lazy.fix[Fix] { lfix => | ||
val lb2f1x = Lazy(run(lfix.value._3, lf0)) | ||
val lb1f2y = Lazy(f(lfix.value._7).run(lb0, lfix.value._5)) | ||
val lb1 = Lazy(lb1f2y.value._1.value) | ||
val lb2 = Lazy(lb2f1x.value._1.value) | ||
val lf1 = Lazy(lb2f1x.value._2.value) | ||
val lf2 = Lazy(lb1f2y.value._2.value) | ||
val lx = Lazy(lb2f1x.value._3.value) | ||
val ly = Lazy(lb1f2y.value._3.value) | ||
(lb2f1x, lb1f2y, lb1, lb2, lf1, lf2, lx, ly) | ||
} | ||
val (_, _, _, lb2, _, lf2, _, ly) = lfix.value | ||
(lb2, lf2, ly) | ||
} | ||
} | ||
|
||
object Tardis { | ||
|
||
/** Returns a new tardis monad holds the specified value as its result. */ | ||
def pure[BW, FW, A](lx: Lazy[A]): Tardis[BW, FW, A] = Tardis((lb, lf) => (lb, lf, lx)) | ||
|
||
/** Returns a new tardis monad holds a backward state as its result. */ | ||
def getBackward[BW, FW]: Tardis[BW, FW, BW] = Tardis((lb, lf) => (lb, lf, lb)) | ||
|
||
/** Returns a new tardis monad for putting the specified value to a backward state. */ | ||
def putBackward[BW, FW](lb: Lazy[BW]): Tardis[BW, FW, Unit] = Tardis((_, lf) => (lb, lf, Lazy(()))) | ||
|
||
/** Returns a new tardis monad for modifying a backward state by the given mapping. */ | ||
def modifyBackward[BW, FW](f: Lazy[BW] => Lazy[BW]): Tardis[BW, FW, Unit] = | ||
Tardis((lb, lf) => (f(lb), lf, Lazy(()))) | ||
|
||
/** Returns a new tardis monad holds a forward state as its result. */ | ||
def getForward[BW, FW]: Tardis[BW, FW, FW] = Tardis((lb, lf) => (lb, lf, lf)) | ||
|
||
/** Returns a new tardis monad for putting the specified value to a forward state. */ | ||
def putForward[BW, FW](lf: Lazy[FW]): Tardis[BW, FW, Unit] = Tardis((lb, _) => (lb, lf, Lazy(()))) | ||
|
||
/** Returns a new tardis monad for modifying a forward state by the given mapping. */ | ||
def modifyForward[BW, FW](f: Lazy[FW] => Lazy[FW]): Tardis[BW, FW, Unit] = | ||
Tardis((lb, lf) => (lb, f(lf), Lazy(()))) | ||
} |
12 changes: 12 additions & 0 deletions
12
modules/lite-crazy/shared/src/main/scala/codes/quine/labo/lite/crazy/implicits.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,12 @@ | ||
package codes.quine.labo.lite.crazy | ||
|
||
/** implicits provides convenience implicit conversions. */ | ||
object implicits { | ||
import scala.language.implicitConversions | ||
|
||
/** Wraps any value to lazy cell. */ | ||
implicit def any2lazy[A](x: => A): Lazy[A] = Lazy(x) | ||
|
||
/** Unwraps any value from a lazy cell. */ | ||
implicit def lazy2any[A](lx: Lazy[A]): A = lx.value | ||
} |
28 changes: 28 additions & 0 deletions
28
modules/lite-crazy/shared/src/test/scala/codes/quine/labo/lite/crazy/LazySuite.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,28 @@ | ||
package codes.quine.labo.lite.crazy | ||
|
||
class LazySuite extends munit.FunSuite { | ||
test("Lazy.apply") { | ||
var x = 0 | ||
val lx = Lazy { | ||
x += 1 | ||
x | ||
} | ||
assertEquals(x, 0) | ||
assertEquals(lx.value, 1) | ||
assertEquals(x, 1) | ||
assertEquals(lx.value, 1) | ||
} | ||
|
||
test("Lazy.fix") { | ||
val lxs = Lazy.fix[LazyList[Int]](lxs => LazyList.cons(1, lxs.value).scanLeft(0)(_ + _)) | ||
assertEquals(lxs.value.take(10), LazyList(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)) | ||
} | ||
|
||
test("Lazy#map") { | ||
assertEquals(Lazy(1).map(_ + 2).value, 3) | ||
} | ||
|
||
test("Lazy#flatMap") { | ||
assertEquals(Lazy(1).flatMap(x => Lazy(x + 2)).value, 3) | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
modules/lite-crazy/shared/src/test/scala/codes/quine/labo/lite/crazy/RStateSuite.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,21 @@ | ||
package codes.quine.labo.lite.crazy | ||
|
||
class RStateSuite extends munit.FunSuite { | ||
// https://lukepalmer.wordpress.com/2008/08/10/mindfuck-the-reverse-state-monad/ | ||
test("RState: fibs") { | ||
import codes.quine.labo.lite.crazy.implicits._ | ||
|
||
def cumulativeSums(xs: LazyList[Int]): LazyList[Int] = xs.scanLeft(0)(_ + _) | ||
|
||
def computeFibs(): RState[LazyList[Int], LazyList[Int]] = for { | ||
fibs <- RState.get[LazyList[Int]] | ||
_ <- RState.modify[LazyList[Int]](cumulativeSums(_)) | ||
_ <- RState.put[LazyList[Int]](LazyList.cons(1, fibs)) | ||
} yield fibs | ||
|
||
assertEquals( | ||
computeFibs().run(LazyList.empty[Int])._2.take(15), | ||
LazyList(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377) | ||
) | ||
} | ||
} |
Oops, something went wrong.