├── .gitignore ├── README.md ├── build.sbt ├── project └── build.properties └── src └── main └── scala └── coreader ├── console.scala ├── main.scala ├── package.scala └── world.scala /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is an exploration of using Cokleisli instead of Kliesli 2 | as a replacement for "Dependency injection" or whatever. 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | name := "scala-coreader" 2 | 3 | scalaVersion := "2.11.8" 4 | 5 | libraryDependencies += "org.typelevel" %% "cats-core" % "0.4.0" 6 | 7 | 8 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.11 -------------------------------------------------------------------------------- /src/main/scala/coreader/console.scala: -------------------------------------------------------------------------------- 1 | package coreader 2 | 3 | import cats._ 4 | import cats.data._ 5 | import cats.arrow._ 6 | 7 | /** 8 | * This is (uh, I think?) a coalgebra represending read and write 9 | * operations to a console. 10 | */ 11 | trait Console[A] { 12 | // this is the comonadic copoint 13 | def extract: A 14 | 15 | // write a string to the console 16 | def write: Unit 17 | 18 | // read a string from the user 19 | def read: String 20 | 21 | // Console is Functorial 22 | def map[B](f: A => B): Console[B] 23 | 24 | // Console is Comonadic 25 | def coflatMap[B](f: Console[A] => B): Console[B] 26 | } 27 | 28 | /** 29 | * An implementation of console which actually interacts with the 30 | * console, side-effecting. 31 | */ 32 | case class RealConsole[A](extract: A) extends Console[A] { 33 | override def write: Unit = println(extract) 34 | override def read: String = scala.Console.readLine 35 | 36 | override def map[B](f: A => B): RealConsole[B] = 37 | RealConsole(f(extract)) 38 | 39 | override def coflatMap[B](f: Console[A] => B): RealConsole[B] = 40 | RealConsole(f(this)) 41 | } 42 | 43 | object Console { 44 | type ConsoleApp[A,B] = Cokleisli[Console, A, B] 45 | 46 | /** read the A value */ 47 | def extract[A]: Cokleisli[Console, A, A] = 48 | Cokleisli(_.extract) 49 | 50 | def write: ConsoleApp[String,Unit] = Cokleisli(_.write) 51 | def read: ConsoleApp[Unit,String] = Cokleisli(_.read) 52 | 53 | implicit val consoleInstances: Comonad[Console] = new Comonad[Console] { 54 | override def extract[A](fa: Console[A]): A = fa.extract 55 | override def map[A,B](fa: Console[A])(f: A => B): Console[B] = fa map f 56 | override def coflatMap[A,B](fa: Console[A])(f: Console[A] => B): Console[B] = fa coflatMap f 57 | } 58 | 59 | implicit val fromWorld: World ~> Console = new NaturalTransformation[World,Console] { 60 | def apply[A](w: World[A]): Console[A] = w.console 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/main/scala/coreader/main.scala: -------------------------------------------------------------------------------- 1 | package coreader 2 | 3 | import cats._ 4 | import cats.data._ 5 | import cats.implicits._ 6 | 7 | object App { 8 | /** A path from A to B in some World */ 9 | type App[A,B] = Cokleisli[World, A, B] 10 | 11 | /** read the A value */ 12 | def extract[A]: App[A,A] = 13 | Cokleisli(_.extract) 14 | } 15 | 16 | object Main { 17 | import App._ 18 | 19 | val askName: App[Unit, String] = 20 | (Cokleisli.pure("what is your name") andThen Console.write andThen Console.read).local 21 | 22 | val sayHello: App[String, Unit] = 23 | (Console.extract andThen Console.write.lmap[String]("Hello, " + _)).local 24 | 25 | /** 26 | * Ask the user their name, then give them a personalized greeting 27 | */ 28 | val app = (askName andThen sayHello) 29 | 30 | def main(argv: Array[String]): Unit = { 31 | app.run(RealWorld.bigBang) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/coreader/package.scala: -------------------------------------------------------------------------------- 1 | import cats.data._ 2 | import cats._ 3 | 4 | package object coreader { 5 | implicit class CokleisliOps[F[_],A,B](ck: Cokleisli[F,A,B]) { 6 | def local[G[_]](implicit nt: G ~> F): Cokleisli[G,A,B] = Cokleisli(g => ck.run(nt(g))) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/coreader/world.scala: -------------------------------------------------------------------------------- 1 | package coreader 2 | 3 | import cats._ 4 | 5 | /** 6 | * A trait that allows me to interact with the world 7 | */ 8 | trait World[A] { 9 | def extract: A 10 | def console: Console[A] 11 | 12 | def map[B](f: A => B): World[B] 13 | def coflatMap[B](f: World[A] => B): World[B] 14 | } 15 | 16 | /** 17 | * A World implementation that actually interacts with the real world 18 | */ 19 | case class RealWorld[A](extract: A) extends World[A] { 20 | override def console: Console[A] = RealConsole(extract) 21 | 22 | override def map[B](f: A => B): RealWorld[B] = 23 | RealWorld(f(extract)) 24 | 25 | override def coflatMap[B](f: World[A] => B): RealWorld[B] = 26 | RealWorld(f(this)) 27 | } 28 | 29 | object RealWorld { 30 | def bigBang = RealWorld(()) 31 | } 32 | 33 | object World { 34 | 35 | implicit val worldInstances: Comonad[World] = new Comonad[World] { 36 | override def extract[A](fa: World[A]): A = fa.extract 37 | override def map[A,B](fa: World[A])(f: A => B): World[B] = fa map f 38 | override def coflatMap[A,B](fa: World[A])(f: World[A] => B): World[B] = fa coflatMap f 39 | } 40 | } 41 | --------------------------------------------------------------------------------