Skip to content

Instantly share code, notes, and snippets.

@sshark
Last active May 18, 2016 11:38
Show Gist options
  • Save sshark/e7d2a8c561cc972407e96b16517f4a24 to your computer and use it in GitHub Desktop.
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
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