Last active
May 18, 2016 11:38
-
-
Save sshark/e7d2a8c561cc972407e96b16517f4a24 to your computer and use it in GitHub Desktop.
How to use Scalaz EitherT and \/ both monadically and applicatively to validate inputs
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
import scalaz._ | |
import Scalaz._ | |
import scala.concurrent.Future | |
import scala.concurrent.ExecutionContext.Implicits.global | |
case class Order(id: Long, desc: String) | |
case class Vendor(id: Long, name: String) | |
def delay[A](f: => Future[A], duration: Long, message: String) = { | |
println(message) | |
val result = f | |
Thread.sleep(duration) | |
result | |
} | |
def extractHeader(valid: Boolean): Option[String] = { | |
println("#header") | |
Thread.sleep(5000) | |
if (valid) Some("bob") else None | |
} | |
def retrieveVendor(username: String, found: Boolean): Future[Option[Vendor]] = | |
delay[Option[Vendor]](if (found) Future.successful(Some(Vendor(103, "Bob Enterprise"))) else Future.successful(None), | |
3000, "#vendor") | |
def retrieveOrders(vendorId: Long, found: Boolean): Future[List[Order]] = | |
delay[List[Order]](if (found) Future.successful(List(Order(1, "Product 1"), Order(2, "Product 2"))) | |
else Future.successful(List.empty[Order]), 1000, "#orders") | |
def output(result: Future[\/[String, AnyRef]]) = result.foreach { | |
case -\/(s) => println(s"Error: $s") | |
case \/-(ys @ x :: xs) => println(ys) | |
case \/-(s) => println(s) | |
} | |
// Monadic form runs sequentially though Future was used | |
def orders(valid: Boolean, userFound: Boolean, vendorFound: Boolean) = for { | |
username <- EitherT(Future(extractHeader(valid) \/> "Bad headers")) // Future[Option[String]] | |
vendor <- EitherT(retrieveVendor(username, userFound).map(_ \/> "Bad username")) // Future[Option[Vendor]] | |
orders <- EitherT(retrieveOrders(vendor.id, vendorFound).map { // Future[Seq[Order]] | |
case Nil => "Bad vendor".left | |
case xs => xs.right | |
}) | |
} yield orders | |
output(orders(true,true, true).run) | |
// List(Order(1,Product 1), Order(2,Product 2)) | |
output(orders(false,false, true).run) | |
// Error: Bad headers | |
// Applicative form runs in parallel | |
def applicativeOrders(valid: Boolean, userFound: Boolean, vendorFound: Boolean) = { | |
val usernameF = Future((extractHeader(valid) \/> "Bad headers").validationNel) | |
val vendorF = retrieveVendor("100", userFound).map(x => (x \/> "Bad username").validationNel) | |
val ordersF = retrieveOrders(100, vendorFound).map { | |
case Nil => "Bad vendor".left[List[Order]].validationNel | |
case xs => xs.right[String].validationNel | |
} | |
for { | |
username <- usernameF | |
vendor <- vendorF | |
orders <- ordersF | |
} yield (username |@| vendor |@| orders) | |
} | |
applicativeOrders(true,true, true).map(_.apply((x,y,z) => s"$x ## $y ## #z")).foreach(println) | |
applicativeOrders(false,false, true).map(_.apply((x,y,z) => s"$x ## $y ## #z")).foreach(println) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment