-
Notifications
You must be signed in to change notification settings - Fork 17
WIP: Towards pure FP using IO Monads and Kleisli composition #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
db486a8
3f4266a
ca693dc
1ddc528
33bc103
52a4925
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package io.github.sentenza.hacktoberfest.fplibrary | ||
|
|
||
| final case class IO[+A](unsafeRun: () => A) extends AnyVal | ||
|
|
||
| object IO { | ||
| // private type Thunk[A] = () => A | ||
| // type Description[A] = Thunk[A] | ||
|
|
||
| def create[A](a: => A): IO[A] = | ||
| IO(() => a) | ||
|
|
||
| implicit val M: Monad[IO] = new Monad[IO] { | ||
| final override def flatMap[A, B](ca: IO[A])(acb: A => IO[B]): IO[B] = | ||
| IO.create { | ||
| val a = ca.unsafeRun() | ||
| val db = acb(a) | ||
|
|
||
| val b = db.unsafeRun() | ||
|
|
||
| b | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package io.github.sentenza.hacktoberfest.fplibrary | ||
|
|
||
| trait Monad[C[_]] { | ||
| // We needed this helper method to compose functions | ||
| // def helper[A, B](ca: C[A], acb: A => C[B]): C[B] // From Helper to FlatMap | ||
|
|
||
| def flatMap[A, B](ca: C[A])(acb: A => C[B]): C[B] // Curried version of helper method | ||
| // haskell bind | ||
| @inline def bind[A, B](ca: C[A])(acb: A => C[B]): C[B] = flatMap(ca)(acb) | ||
| @inline def >>=[A, B](ca: C[A])(acb: A => C[B]): C[B] = flatMap(ca)(acb) // >>= Haskell Logo | ||
| } | ||
|
|
||
| // Type classes and implicitly | ||
| object Monad { | ||
| def apply[C[_]: Monad]: Monad[C] = implicitly[Monad[C]] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package io.github.sentenza.hacktoberfest.fplibrary | ||
|
|
||
| object PointFree { | ||
| // val ac = compose(ab, bc) | ||
| def compose[A, B, C](ab: A => B, bc: B => C): A => C = a => { | ||
| // taking A in and producing b | ||
| val b = ab(a) | ||
| // Going from B to C | ||
| val c = bc(b) | ||
| // Returning C | ||
| c | ||
| } | ||
|
|
||
| def composeKleisliOld[A, B, C]( | ||
| adb: A => IO[B], | ||
| bdc: B => IO[C] | ||
| ): A => IO[C] = a => { | ||
| val db: IO[B] = adb(a) | ||
| val b = db.unsafeRun() | ||
|
|
||
| val dc = bdc(b) | ||
|
|
||
| dc | ||
| } | ||
|
|
||
| // Kleisli Composition won't actually work without Monads (try to remove Monad from D[_]: Monad) | ||
| def composeKleisli[A, B, C, D[_]: Monad](adb: A => D[B], bdc: B => D[C]): A => D[C] = a => { | ||
| val db: D[B] = adb(a) | ||
| val dc = Monad[D].flatMap(db)(bdc) | ||
| dc | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package io.github.sentenza.hacktoberfest | ||
|
|
||
| package object fplibrary { | ||
| // private type Thunk[A] = () => A | ||
| // type Description[A] = Thunk[A] | ||
| private type RegularArrow[A, B] = A => B | ||
| private type KleisliArrow[A, B, C[_]] = A => C[B] | ||
|
|
||
| // Why are we extending AnyVal? You said performance, why? | ||
| // val ac = ab `;` bc | ||
| implicit final class InfixNotationForPointFree[A, B](private val ab: A => B) extends AnyVal { | ||
| // Alias: In Haskell compose is a simple dot | ||
| @inline def `.`[C](bc: B => C): A => C = PointFree.compose(ab, bc) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't this be simply @inline def `.`[C](bc: B => C): A => C = bc.compose(ab)?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also be very careful with this choice, since the Also considering that
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess that it would be a better choice to use |
||
| // Let's also define an Arrow | ||
| @inline def -->[C](bc: B => C): A => C = PointFree.compose(ab, bc) | ||
| } | ||
|
|
||
| implicit final class InfixNotationForPointFreeKleisli[A, B, D[_]](private val adb: A => D[B]) | ||
| extends AnyVal { | ||
| // Haskell Fish | ||
| @inline def >=>[C](bdc: B => D[C])(implicit M: Monad[D]): A => D[C] = | ||
| PointFree.composeKleisli(adb, bdc) | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package io.github.sentenza.hacktoberfest.application | ||
|
|
||
| import io.github.sentenza.hacktoberfest.fplibrary._ | ||
|
|
||
| object Program { | ||
| // def createIO(args: Array[String]): IO[Unit] = | ||
| // () => displayMessages() | ||
|
|
||
| lazy val unreadableCreateIO: Array[String] => IO[Unit] = args => | ||
| IO.create( | ||
| display( | ||
| hyphens( | ||
| display( | ||
| createMessage( | ||
| display( | ||
| hyphens( | ||
| args | ||
| ) | ||
| ) | ||
| ) | ||
| ) | ||
| ) | ||
| ) | ||
| ) | ||
|
|
||
| // lazy val createDescription: Array[String] => IO[Unit] = | ||
| // ignoreArgs --> hyphens --> displayKleisli >=> | ||
| // question --> displayKleisli >=> | ||
| // promptKleisli >=> | ||
| // convertStringToInt --> ensureAmountIsPositive --> round --> createMessage --> displayKleisli >=> | ||
| // hyphens --> displayKleisli | ||
|
|
||
| // --> is equal to the std library "andThen" | ||
|
|
||
| // Format: OFF | ||
| lazy val createIO: Array[String] => IO[Unit] = | ||
| ignoreArgs --> hyphens --> displayKleisli >=> | ||
| createMessage --> displayKleisli >=> | ||
| hyphens --> displayKleisli | ||
| //Format: ON | ||
|
|
||
| // Transforming defs into functions - Old version had def | ||
| // The following values were actually defs and now we transformed them into functions | ||
| // Functions from A to B: A => B | ||
| // private def hyphens(input: Any): String = "\u2500" * 50 | ||
| private lazy val hyphens: Any => String = _ => "\u2500" * 50 | ||
| private lazy val createMessage: Any => String = _ => "Welcome to HacktoberFest Scala Algorhitms!" | ||
| private lazy val display: Any => Unit = input => println(input) | ||
| private lazy val prompt: Any => String = _ => scala.io.StdIn.readLine() | ||
| private lazy val ignoreArgs: Array[String] => Unit = _ => () | ||
|
|
||
| // Kleisli | ||
| private lazy val displayKleisli: Any => IO[Unit] = input => | ||
| IO.create(println(input)) | ||
| private lazy val promptKleisli: Any => IO[String] = _ => | ||
| IO.create(scala.io.StdIn.readLine()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package io.github.sentenza.hacktoberfest.eotw | ||
|
|
||
| import io.github.sentenza.hacktoberfest.fplibrary._ | ||
| import io.github.sentenza.hacktoberfest.application.Program | ||
|
|
||
| object Interpreter { | ||
| def main(args: Array[String]): Unit = { | ||
| val description: IO[Unit] = Program.createIO(args) | ||
| def interpret[A](desc: IO[A]): A = desc.unsafeRun() | ||
| print(Console.YELLOW) | ||
sentenza marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| printDisclaimer() | ||
| print(Console.RED) | ||
| // <==== If you saw some red text you would know that something bad is going on ====> | ||
| print(Console.GREEN) | ||
| interpret(description) | ||
| print(Console.RESET) | ||
| } | ||
|
|
||
| private def printDisclaimer(): Unit = { | ||
| println(heading + gplDisclaimer) | ||
| } | ||
|
|
||
| private val heading = | ||
| """ | ||
| _ _ _ _ _ ______ _ | ||
| | | | | | | | | | | | ___| | | | ||
| | |_| | __ _ ___| | _| |_ ___ | |__ ___ _ __| |_ ___ ___| |_ | ||
| | _ |/ _` |/ __| |/ / __/ _ \| '_ \ / _ \ '__| _/ _ \/ __| __| | ||
| | | | | (_| | (__| <| || (_) | |_) | __/ | | || __/\__ \ |_ | ||
| \_| |_/\__,_|\___|_|\_\\__\___/|_.__/ \___|_| \_| \___||___/\__| | ||
|
|
||
| """ | ||
|
|
||
| private val gplDisclaimer = | ||
| """ | ||
| HacktoberFest Scala Algorhitms Copyright (C) 2018-2019 @sentenza | ||
| This program comes with ABSOLUTELY NO WARRANTY. | ||
| This is free software, and you are welcome to redistribute it | ||
| under certain conditions. All the details can be found at: | ||
| https://github.com/sentenza/hacktoberfest-scala-algorithms/blob/master/LICENSE. | ||
| """ | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,56 @@ | ||
| scalaVersion := "2.13.1" | ||
| name := "hacktoberfest-algorithms" | ||
| organization := "hacktoberfest" | ||
| ThisBuild / scalaVersion := "2.13.1" | ||
| ThisBuild / name := "hacktoberfest-algorithms" | ||
| ThisBuild / organization := "hacktoberfest" | ||
| // We will use Semver for this project | ||
| version := "0.11.0" | ||
| ThisBuild / version := "0.12.0" | ||
|
|
||
| // libraries | ||
| val scalaTestVersion = "3.0.8" | ||
| val ammoniteVersion = "1.7.4" | ||
|
|
||
| libraryDependencies += "org.scalatest" %% "scalatest" % scalaTestVersion % "test" | ||
| libraryDependencies += "com.lihaoyi" % "ammonite" % ammoniteVersion % "test" cross CrossVersion.full | ||
|
|
||
| // Fancy SBT functions | ||
| watchTriggeredMessage := Watch.clearScreenOnTrigger | ||
| shellPrompt := (_ => fancyPrompt(name.value)) | ||
|
|
||
| def cyan(projectName: String): String = | ||
| scala.Console.CYAN + projectName + scala.Console.RESET | ||
|
|
||
| def fancyPrompt(projectName: String): String = | ||
| s"""| | ||
| |[info] Welcome to the ${cyan(projectName)} project! | ||
| |sbt> """.stripMargin | ||
|
|
||
| // Projects | ||
| lazy val `fp-library` = | ||
| project | ||
| .in(file("./1-fp-library")) | ||
| .settings( | ||
| shellPrompt := (_ => fancyPrompt(name.value)) | ||
| ) | ||
|
|
||
| lazy val `application-library` = | ||
| project | ||
| .in(file("./2-application-library")) | ||
| .settings(shellPrompt := (_ => fancyPrompt(name.value))) | ||
| .dependsOn(`fp-library`) | ||
|
|
||
| // This project should contain just the main method | ||
| lazy val `end-of-the-world` = | ||
| project | ||
| .in(file("./3-end-of-the-world")) | ||
| .settings(shellPrompt := (_ => fancyPrompt(name.value))) | ||
| .dependsOn(`application-library`) | ||
|
|
||
| // Command Aliases | ||
| addCommandAlias("ll", "projects") | ||
|
|
||
| addCommandAlias("cd", "project") | ||
|
|
||
| addCommandAlias("lib", "project fp-library") | ||
|
|
||
| addCommandAlias("app", "project application-library") | ||
|
|
||
| addCommandAlias("eow", "project end-of-the-world") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| sbt.version=1.3.2 | ||
| sbt.version=1.3.3 |
Uh oh!
There was an error while loading. Please reload this page.