Created
May 12, 2017 16:19
-
-
Save noelwelsh/18b19947a3e206ae7e19c01b197f8493 to your computer and use it in GitHub Desktop.
Simple website login implemented using the free monad
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
object Website { | |
import cats.free.Free | |
import cats.Comonad | |
import scala.io._ | |
final case class User(username: String) | |
sealed trait Page | |
final case object Welcome extends Page | |
final case object TryAgain extends Page | |
type LoginProgram[A] = Free[LoginOp,A] | |
sealed trait LoginOp[A] | |
final case class Ask[A](prompt: String, reader: String => A) extends LoginOp[A] | |
final case class Retry[A,B](ask: LoginProgram[A], f: A => LoginProgram[Option[B]], tries: Int) extends LoginOp[Option[B]] | |
final case class Login(username: String, password: String) extends LoginOp[Option[User]] | |
final case class Pure[A](a: A) extends LoginOp[A] | |
final case class Display(page: Page) extends LoginOp[Unit] | |
object LoginOp { | |
def ask[A](prompt: String, reader: String => A) = | |
Free.liftF[LoginOp,A](Ask(prompt, reader)) | |
def retry[A,B](ask: LoginProgram[A], f: A => LoginProgram[Option[B]], tries: Int) = | |
Free.liftF[LoginOp,Option[B]](Retry(ask, f, tries)) | |
def login(username: String, password: String) = | |
Free.liftF[LoginOp,Option[User]](Login(username, password)) | |
def display(page: Page) = | |
Free.liftF[LoginOp,Unit](Display(page)) | |
implicit object loginOpInstances extends Comonad[LoginOp] { | |
override def coflatMap[A, B](fa: LoginOp[A])(f: (LoginOp[A]) ⇒ B): LoginOp[B] = | |
Pure(f(fa)) | |
override def extract[A](x: LoginOp[A]): A = | |
x match { | |
case Login(u, p) => (u, p) match { | |
case ("Noel", "password") => Some(User("noelw")) | |
case _ => None | |
} | |
case Display(p) => | |
p match { | |
case TryAgain => | |
println("Sorry, couldn't login you in. Try again!") | |
case Welcome => | |
println("Welcome back!") | |
} | |
() | |
case Ask(p, r) => | |
println(p) | |
r(StdIn.readLine) | |
case r: Retry[a,b] => | |
def loop(counter: Int): Option[b] = | |
counter match { | |
case 0 => None | |
case n => r.f(r.ask.run: a).run match { | |
case None => loop(n - 1) | |
case Some(a) => Some(a) | |
} | |
} | |
loop(r.tries) | |
case Pure(a) => a | |
} | |
override def map[A, B](fa: LoginOp[A])(f: (A) ⇒ B): LoginOp[B] = | |
Pure(f(extract(fa))) | |
} | |
} | |
val loginPrompt = LoginOp.ask("Enter login stuff", (s: String) => { | |
val split = s.split(" ") | |
(split(0), split(1)) | |
}) | |
def loginAttempt(details: (String, String)) = { | |
val (u, p) = details | |
LoginOp.login(u, p) | |
} | |
val attemptLogin = LoginOp.retry(loginPrompt, loginAttempt, 3) | |
object Example { | |
val login = | |
for { | |
user <- attemptLogin | |
page <- LoginOp.display(user.fold[Page](TryAgain){ u => Welcome }) | |
} yield page | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment