Skip to content
This repository has been archived by the owner on Jan 26, 2022. It is now read-only.

Commit

Permalink
Merge pull request #28 from MakeNowJust-Labo/lazy
Browse files Browse the repository at this point in the history
  • Loading branch information
makenowjust committed Aug 1, 2021
2 parents 38a9425 + 4b34a83 commit 64f9cfc
Show file tree
Hide file tree
Showing 13 changed files with 362 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Changes:
- `lite-diff`: Initial release ([#16](https://github.com/MakeNowJust-Labo/lite/pull/16))
- `lite-pfix`: Initial release ([#27](https://github.com/MakeNowJust-Labo/lite/pull/27))
- `lite-shohw`: Use `lite-pfix` ([#27](https://github.com/MakeNowJust-Labo/lite/pull/27))
- `lite-crazy`: Initial release ([#28](https://github.com/MakeNowJust-Labo/lite/pull/28))
- Support Scala 2.13.6
- Support Scala 3.0.1 ([#23](https://github.com/MakeNowJust-Labo/lite/pull/23))
- Update sbt to 1.5.5 ([#24](https://github.com/MakeNowJust-Labo/lite/pull/24))
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

## Libraries

- [**lite-crazy**](modules/lite-crazy): A lazy-evaluated value cell and time travelling state monads implementations.
- [**lite-diff**](modules/lite-diff): Computes a diff between two sequences.
- [**lite-gimei**](modules/lite-gimei): A generator of Japanese dummy names and addresses with furigana.
- [**lite-grapheme**](modules/lite-grapheme): Iterates the given string on each grapheme cluster.
Expand Down
32 changes: 31 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ ThisBuild / semanticdbVersion := scalafixSemanticdb.revision
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.5.0"
ThisBuild / scalafixDependencies += "com.github.vovapolu" %% "scaluzzi" % "0.1.18"

val crossProjectNames = Seq("diff", "gimei", "grapheme", "romaji", "show")
val crossProjectNames = Seq("crazy", "diff", "gimei", "grapheme", "pfix", "romaji", "show")
val platformSuffices = Seq("JVM", "JS", "Native")
platformSuffices.flatMap { platform =>
addCommandAlias(s"test$platform", crossProjectNames.map(name => s"$name$platform/test").mkString("; "))
Expand All @@ -42,12 +42,42 @@ lazy val root = project
publish / skip := true,
coverageEnabled := false
)
.aggregate(crazyJVM, crazyJS, crazyNative)
.aggregate(diffJVM, diffJS, diffNative)
.aggregate(gimeiJVM, gimeiJS, gimeiNative)
.aggregate(graphemeJVM, graphemeJS, graphemeNative)
.aggregate(romajiJVM, romajiJS, romajiNative)
.aggregate(pfixJVM, pfixJS, pfixNative)
.aggregate(showJVM, showJS, showNative)

lazy val crazy = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.in(file("modules/lite-crazy"))
.settings(
name := "lite-crazy",
console / initialCommands :=
"""|import codes.quine.labo.lite.crazy._
|""".stripMargin,
Compile / console / scalacOptions -= "-Wunused",
Test / console / scalacOptions -= "-Wunused",
// Set URL mapping of scala standard API for Scaladoc.
apiMappings ++= scalaInstance.value.libraryJars
.filter(file => file.getName.startsWith("scala-library") && file.getName.endsWith(".jar"))
.map(_ -> url(s"http://www.scala-lang.org/api/${scalaVersion.value}/"))
.toMap,
// Settings for test:
libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test,
testFrameworks += new TestFramework("munit.Framework")
)
.jsSettings(Test / scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) })
.nativeSettings(
crossScalaVersions := Seq("2.13.6"),
coverageEnabled := false
)

lazy val crazyJVM = crazy.jvm
lazy val crazyJS = crazy.js
lazy val crazyNative = crazy.native

lazy val diff = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.in(file("modules/lite-diff"))
.settings(
Expand Down
18 changes: 18 additions & 0 deletions modules/lite-crazy/README.md
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/>
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
}
}
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(())))
}
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(())))
}
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
}
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)
}
}
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)
)
}
}
Loading

0 comments on commit 64f9cfc

Please sign in to comment.