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

Add lite-crazy module #28

Merged
merged 6 commits into from
Aug 1, 2021
Merged
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
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