an excuse to try, either, folding, and future. sequence
DESCRIPTION
Some example usage of Scala and FP, using as an excuse some code that I wrote to parse command line options.TRANSCRIPT
An excuse to Try, Either, folding, and Future.
sequence or parsing command line options with
crappy-optional
@gerferra
crappy-optional?
“Don’t worry be crappy” mantra
optional library from DRMacIverand paulp
optional
optional is a command line option parser and library.
optional
YOU WRITE:
object MyAwesomeCommandLineTool extends optional.Application {
// for instance...
def main(count: Option[Int], file: Option[java.io.File],
arg1: String) {
[...]
}
}
optional
THEN YOU DO:
scala MyAwesomeCommandLineTool --count 5 quux
optional
AND YOUR MAIN METHOD WILL BE INVOKED SUCH THAT:
count = Some(5)file = Nonearg1 = quux
optional
AND YOUR MAIN METHOD WILL BE INVOKED SUCH THAT:
count = Some(5)file = Nonearg1 = quux
object MyAwesomeCommandLineTool extends optional.Application {
// for instance...
def main(count: Option[Int], file: Option[java.io.File],
arg1: String) {
[...]
}
}
scala MyAwesomeCommandLineTool --count 5 quux
Ref by name
optional
AND YOUR MAIN METHOD WILL BE INVOKED SUCH THAT:
count = Some(5)file = Nonearg1 = quux
object MyAwesomeCommandLineTool extends optional.Application {
// for instance...
def main(count: Option[Int], file: Option[java.io.File],
arg1: String) {
[...]
}
}
scala MyAwesomeCommandLineTool --count 5 quux
Ref by name
Optional parameter
optional
AND YOUR MAIN METHOD WILL BE INVOKED SUCH THAT:
count = Some(5)file = Nonearg1 = quux
object MyAwesomeCommandLineTool extends optional.Application {
// for instance...
def main(count: Option[Int], file: Option[java.io.File],
arg1: String) {
[...]
}
}
scala MyAwesomeCommandLineTool --count 5 quux
Ref by name
Optional parameter
Ref by position
optional
HOW IT WORKS:
optional
HOW IT WORKS:
Reflection, man.
optional
HOW IT WORKS:
(Java) Reflection, man.
optional
HOW IT WORKS:
(Java) Reflection, man.
doesn’t work OK with Scala since 2.9 (I guess) ...
used to work
So...
… copy the idea, but use Scala reflection instead …
… and don’t worry and be crappy :) ...
Show me the code!trait Application {
def main(args: Array[String]) {
runMain(this, args.toList)
}
private def runMain[T: ClassTag](
instance: T, args: List[String]) { // ... something happens here ...
}
}
Show me the code!private def runMain[T: ClassTag](instance: T, args: List[String]) {
val instanceMirror = ru.runtimeMirror(getClass.getClassLoader).reflect(instance)
val theType = instanceMirror.symbol.typeSignature
val methods = theType.members.collect {
case m if m.isMethod => m.asMethod
}
val mainMethods = methods.filter(m => m.name == ru.TermName("main") &&
m.fullName != classOf[Application].getName + ".main")
val methodOrError = mainMethods.headOption.toRight("No main method defined")
// Scala support multiple parameter lists, I care only about the first (if exists ...)
val = methodOrError.right.map(m => m.paramLists.headOption.getOrElse(Nil))
paramsOrError
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right // mysterious instantiation of things ...
} yield {
val refMethod = instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*) // here is the execution of the main method happening
}
invocationOrError.fold(println, identity /* nothing to do because it already executed as a side effect */)
}
Step by step: method definition
Scala method:
private def runMain[T: ClassTag](
instance: T, args: List[String]) {
private, so it’s not inherited
with a type parameter T
with a context bound ClassTag
Step by step: method definition
… with a context bound ClassTag?
private def runMain[T: ClassTag](
instance: T, args: List[String]) {
≡ private def runMain[T](
instance: T, args: List[String])(
implicit ev: ClassTag[T]) {
both expressions are analogous
Step by step: method definition
we ask for an evidence that a ClassTag can be constructed for the type T ...
… so we can use runtime reflection on it ...
Step by step: reflecting on instance
val instanceMirror =
ru
.runtimeMirror(getClass.getClassLoader)
.reflect(instance)
Step by step: reflecting on instance
val instanceMirror =
ru
.runtimeMirror(getClass.getClassLoader)
.reflect(instance)
(mystic voice) …You need a mirror to access the reflection…
Step by step: reflecting on instance
val instanceMirror =
ru
.runtimeMirror(getClass.getClassLoader)
.reflect(instance)
`ru` here is just an alias to packagescala.reflect.runtime.universe
import scala.reflect.runtime.{ universe => ru }
Step by step: reflecting on instance
val instanceMirror =
ru
.runtimeMirror(getClass.getClassLoader)
.reflect(instance)
A ClassTag is needed here to overcome the erasure of the type at runtime T
Step by step: obtaining the methodI want the methods ...
val theType =
instanceMirror.symbol.typeSignature
val methods = theType.members.collect {
case m if m.isMethod => m.asMethod
}
Step by step: obtaining the method… to get the right main methods ...
val mainMethods =
methods.filter(m =>
m.name == ru.TermName("main") &&
m.fullName !=
classOf[Application].getName + ".main")
Step by step: obtaining the method… to get the right main methods ...
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
Give me either the first of the methods, if there is one, or an error message
Step by step: obtaining the method… to get the right main methods ...
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
Give me either the first of the methods, if there is one, or an error message
Step by step: obtaining the method… to get the right main methods …
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
Give me either the first of the methods, if there is one, or an error message
Step by step: obtaining the method… to get the right main methods …
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
headOption ~ List[A] => Option[A]
Step by step: obtaining the method… to get the right main methods …
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
headOption ~ List[A] => Option[A]
Option[A] = Some(a: A) | None
Step by step: obtaining the method… to get the right main methods …
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
headOption ~ List[A] => Option[A]
not scala (!)
Option[A] = Some(a: A) | None
Step by step: obtaining the method… to get the right main methods …
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
headOption ~ List[A] => Option[A]
def headOption: Option[A] =
if (isEmpty) None else Some(head)
Option[A] = Some(a: A) | None
Step by step: obtaining the method… to get the right main methods …
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
toRight[X] ~ Option[A] => (=> X) => Either[X,A]
Step by step: obtaining the method… to get the right main methods …
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
toRight[X] ~ Option[A] => (=> X) => Either[X,A]
Either[A, B] = Left(a: A) | Right(b: B)
Step by step: obtaining the method… to get the right main methods …
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
toRight[X] ~ Option[A] => (=> X) => Either[X,A]
def toRight[X](left: => X): Either[X,A] =
if (isEmpty) Left(left) else Right(this.get)
Either[A, B] = Left(a: A) | Right(b: B)
Step by step: obtaining the method… to get the right main methods …
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
toRight[X] ~ Option[A] => (=> X) => Either[X,A]
def toRight[X](left: => X): Either[X,A] =
if (isEmpty) Left(left) else Right(this.get)
Either[A, B] = Left(a: A) | Right(b: B)
Either[String, ru.MethodSymbol]
Step by step: obtaining the method… to get the right main methods …
val methodOrError =
mainMethods
.headOption
.toRight("No main method defined")
toRight[X] ~ Option[A] => (=> X) => Either[X,A]
def toRight[X](left: => X): Either[X,A] =
if (isEmpty) Left(left) else Right(this.get)
Either[A, B] = Left(a: A) | Right(b: B)
Either[String, ru.MethodSymbol]
Step by step: obtaining the params
Ok. Now I need the parameters:
val paramsOrError =
methodOrError.right.map(m =>
m.paramLists
.headOption.getOrElse(Nil))
Scala support multiple parameter lists. I care only about the first (if exists …)
Step by step: obtaining the params
Ok. Now I need the parameters:
val paramsOrError =
methodOrError.right.map(m =>
m.paramLists
.headOption.getOrElse(Nil)) Either[String, ru.MethodSymbol]
Step by step: obtaining the params
Ok. Now I need the parameters:
val paramsOrError =
methodOrError.right.map(m =>
m.paramLists
.headOption.getOrElse(Nil)) Either[String, ru.MethodSymbol]
only the right part please
Step by step: obtaining the params
Ok. Now I need the parameters:
val paramsOrError =
methodOrError.right.map(m =>
m.paramLists
.headOption.getOrElse(Nil))
map[X] ~ Either[A,B] => (B => X) => Either[A,X]
Either[String, ru.MethodSymbol]
only the right part please
Step by step: obtaining the params
Ok. Now I need the parameters:
val paramsOrError =
methodOrError.right.map(m =>
m.paramLists
.headOption.getOrElse(Nil))
map[X] ~ Either[A,B] => (B => X) => Either[A,X]
def map[X](f: B => X) = e match {
case Left(a) => Left(a)
case Right(b) => Right(f(b))
}
Either[String, ru.MethodSymbol]
only the right part please
Step by step: obtaining the params
Ok. Now I need the parameters:
val paramsOrError =
methodOrError.right.map(m =>
m.paramLists
.headOption.getOrElse(Nil))
List[List[ru.Symbol]]
Step by step: obtaining the params
Ok. Now I need the parameters:
val paramsOrError =
methodOrError.right.map(m =>
m.paramLists
.headOption.getOrElse(Nil))
Option[List[ru.Symbol]]
List[List[ru.Symbol]]
Step by step: obtaining the params
Ok. Now I need the parameters:
val paramsOrError =
methodOrError.right.map(m =>
m.paramLists
.headOption.getOrElse(Nil))
f: ru.MethodSymbol => List[ru.Symbol]
Step by step: obtaining the params
Ok. Now I need the parameters:
val paramsOrError =
methodOrError.right.map(m =>
m.paramLists
.headOption.getOrElse(Nil))
f: ru.MethodSymbol => List[ru.Symbol]paramsOrError: Either[String, List[ru.Symbol]]
Step by step: obtaining the params
Ok. Now I need the parameters:
val paramsOrError =
methodOrError.right.map(m =>
m.paramLists
.headOption.getOrElse(Nil))
f: ru.MethodSymbol => List[ru.Symbol]paramsOrError: Either[String, List[ru.Symbol]]
paramsOrError have the parameters OR the original error "No main method defined"
Step by step: invocation
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right
} yield {
val refMethod =
instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*)
}
Step by step: invocation
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right
} yield {
val refMethod =
instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*)
}
Same as before
Step by step: invocation
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right
} yield {
val refMethod =
instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*)
}
Same as before
The main method if all is right
Step by step: invocation
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right
} yield {
val refMethod =
instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*)
}
Same as before
The parameters if all is right
Step by step: invocation
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right
} yield {
val refMethod =
instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*)
}
Same as before
The values to use to invoke the method
Step by step: invocation
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right
} yield {
val refMethod =
instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*)
}
Same as before
The values to use to invoke the method if all is right :-)
Step by step: invocation
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right
} yield {
val refMethod =
instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*)
}
Will compute only if all OK
Step by step: invocation
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right
} yield {
val refMethod =
instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*)
}
Either[String, Any]
Step by step: invocation
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right
} yield {
val refMethod =
instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*)
}
Either[String, Any]
The first error that occurs (if any)
Step by step: invocation
val invocationOrError =
for {
method <- methodOrError.right
params <- paramsOrError.right
values <- instantiate(params, args).right
} yield {
val refMethod =
instanceMirror.reflectMethod(method)
refMethod(values.unzip._2: _*)
}
Either[String, Any]
The first error that occurs (if any)
The value returned by the method (if all OK)
Step by step: handling ending cases
invocationOrError.fold(println, identity)
Step by step: handling ending cases
invocationOrError.fold(println, identity)
fold[X] ~
Either[A, B] => (A => X) => (B => X) => X
Step by step: handling ending cases
invocationOrError.fold(println, identity)
fold[X] ~
Either[A, B] => (A => X) => (B => X) => X
If you can convert the value at the left to X, and the value at the right to X, then you can convert an Either[A,B] to X
Step by step: handling ending cases
invocationOrError.fold(println, identity)
fold[X] ~
Either[A, B] => (A => X) => (B => X) => X
If you can convert the value at the left to X, and the value at the right to X, then you can convert an Either[A,B] to X
Step by step: handling ending cases
invocationOrError.fold(println, identity)
fold[X] ~
Either[A, B] => (A => X) => (B => X) => X
If you can convert the value at the left to X, and the value at the right to X, then you can convert an Either[A,B] to X
Step by step: handling ending cases
invocationOrError.fold(println, identity)
fold[X] ~
Either[A, B] => (A => X) => (B => X) => X
If you can convert the value at the left to X, and the value at the right to X, then you can convert an Either[A,B] to X
Step by step: handling ending cases
invocationOrError.fold(println, identity)
fold[X] ~
Either[A, B] => (A => X) => (B => X) => X
def fold[X](fa: A=>X, fb: B=>X) = this match {
case Left(a) => fa(a)
case Right(b) => fb(b)
}
Step by step: handling ending cases
invocationOrError.fold(println, identity)
fold[X] ~
Either[A, B] => (A => X) => (B => X) => X
In this case, if there is an error (a Left value) it prints it to the console. If all went OK (a Right value) it does nothing, because the method was already executed as a side-effect
instantiate?def instantiate(paramList: List[ru.Symbol], argList: List[String]): Either[Error, List[(Name, Any)]] = {
val params = paramList.map(p => p.name.encodedName.toString -> p.typeSignature)
val paramNames = params.unzip._1
val argsOptName = assignNames(argList)
val argsPartition = argsOptName.partition(_._1.isEmpty)
val unnamedArgs = argsPartition._1.unzip._2
val namedArgsTmp = argsPartition._2.collect { case (Some(n), value) => n -> value }
def sortFunc(s: String): Int = { val i = paramNames.indexOf(s); if (i == -1) Int.MaxValue else i }
val namedArgs = namedArgsTmp.sortBy(p => sortFunc(p._1))
val argsNames = namedArgs.unzip._1
val (unreferencedParams, referencedParams) = params.partition(p => !argsNames.contains(p._1))
val unreferenced = instantiateUnreferencedParams(unreferencedParams, unnamedArgs)
val referenced = instantiateParams(referencedParams, namedArgs)
for {
unref <- unreferenced.right
ref <- referenced.right
} yield {
(unref ++ ref).sortBy(p => sortFunc(p._1))
}
}
More ...
assignNames
instantiateUnreferencedParams
instantiateParams
coerce
Example:
Suppose we want to query user information, but it is scattered in multiple servers (ldap and database).
Most of the time we will be searching by name, but sometimes we will want to search by uid or by the user login.
And we want it now! :)
Example: usr
So we create a command `usr` with the following arguments:
name: Option[String]
limit: Option[Int]
uid: Option[String]
login: Option[String]
debug: Option[Boolean]
Example: usr
So we create a command `usr` with the following arguments:
name: Option[String]
limit: Option[Int]
uid: Option[String]
login: Option[String]
debug: Option[Boolean]
limit the number of results
Example: usr
So we create a command `usr` with the following arguments:
name: Option[String]
limit: Option[Int]
uid: Option[String]
login: Option[String]
debug: Option[Boolean] extra debug info?
Example: usr
So we create a command `usr` with the following arguments:
name: Option[String]
limit: Option[Int]
uid: Option[String]
login: Option[String]
debug: Option[Boolean]
First so they can be used without name
Example: usr
So we create a command `usr` with the following arguments:
name: Option[String]
limit: Option[Int]
uid: Option[String]
login: Option[String]
debug: Option[Boolean]
Any individual argument is optional
Example: usr
So we create a command `usr` with the following arguments:
name: Option[String]
limit: Option[Int]
uid: Option[String]
login: Option[String]
debug: Option[Boolean]
String, Int, Boolean ...
Example: usr
Some usage examples:
usr "juan perez"
usr "juan perez" 10
usr --uid jperez
usr --login jperez --limit 10 --debug
Example: usr
object usr extends optional.Application {
def main(name: Option[String], limit: Option[Int], uid: Option[String], login: Option[String], debug: Option[Boolean]) {
// THE LOGIC ...
}}
Example: usrobject usr extends optional.Application {
def main(name: Option[String], limit: Option[Int], uid: Option[String],
login: Option[String], debug: Option[Boolean]) {
if (Seq(name, uid, login).flatten.isEmpty) {
println("You must specify at least one search criteria")
System.exit(1)
}
val effDebug = debug.getOrElse(false)
val futUsrLDAPA = Future { ldapA.findUsr(name, limit, uid, login) }
val futUsrLDAPB = Future { ldapB.findUsr(name, limit, uid, login) }
val futUsrDB = Future { dbase.findUsr(name, limit, uid, login) }
futUsrLDAPA.onComplete(printResult("--- LDAP A ---", debug = effDebug))
futUsrLDAPB.onComplete(printResult("--- LDAP B ---", debug = effDebug))
futUsrDB.onComplete(printResult("--- Data Base ---", debug = effDebug))
val res = Await.ready(
Future.sequence(Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB)), Duration.Inf)
}
}
Example: usrobject usr extends optional.Application {
def main(name: Option[String], limit: Option[Int], uid: Option[String],
login: Option[String], debug: Option[Boolean]) {
if (Seq(name, uid, login).flatten.isEmpty) {
println("You must specify at least one search criteria")
System.exit(1)
}
val effDebug = debug.getOrElse(false)
val futUsrLDAPA = Future { ldapA.findUsr(name, limit, uid, login) }
val futUsrLDAPB = Future { ldapB.findUsr(name, limit, uid, login) }
val futUsrDB = Future { dbase.findUsr(name, limit, uid, login) }
futUsrLDAPA.onComplete(printResult("--- LDAP A ---", debug = effDebug))
futUsrLDAPB.onComplete(printResult("--- LDAP B ---", debug = effDebug))
futUsrDB.onComplete(printResult("--- Data Base ---", debug = effDebug))
val res = Await.ready(
Future.sequence(Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB)), Duration.Inf)
}
}
One of them must be specified
Example: usrobject usr extends optional.Application {
def main(name: Option[String], limit: Option[Int], uid: Option[String],
login: Option[String], debug: Option[Boolean]) {
if (Seq(name, uid, login).flatten.isEmpty) {
println("You must specify at least one search criteria")
System.exit(1)
}
val effDebug = debug.getOrElse(false)
val futUsrLDAPA = Future { ldapA.findUsr(name, limit, uid, login) }
val futUsrLDAPB = Future { ldapB.findUsr(name, limit, uid, login) }
val futUsrDB = Future { dbase.findUsr(name, limit, uid, login) }
futUsrLDAPA.onComplete(printResult("--- LDAP A ---", debug = effDebug))
futUsrLDAPB.onComplete(printResult("--- LDAP B ---", debug = effDebug))
futUsrDB.onComplete(printResult("--- Data Base ---", debug = effDebug))
val res = Await.ready(
Future.sequence(Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB)), Duration.Inf)
}
}
No debug by default
Example: usrobject usr extends optional.Application {
def main(name: Option[String], limit: Option[Int], uid: Option[String],
login: Option[String], debug: Option[Boolean]) {
if (Seq(name, uid, login).flatten.isEmpty) {
println("You must specify at least one search criteria")
System.exit(1)
}
val effDebug = debug.getOrElse(false)
val futUsrLDAPA = Future { ldapA.findUsr(name, limit, uid, login) }
val futUsrLDAPB = Future { ldapB.findUsr(name, limit, uid, login) }
val futUsrDB = Future { dbase.findUsr(name, limit, uid, login) }
futUsrLDAPA.onComplete(printResult("--- LDAP A ---", debug = effDebug))
futUsrLDAPB.onComplete(printResult("--- LDAP B ---", debug = effDebug))
futUsrDB.onComplete(printResult("--- Data Base ---", debug = effDebug))
val res = Await.ready(
Future.sequence(Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB)), Duration.Inf)
}
}
Query the three sources in parallel
Example: usrobject usr extends optional.Application {
def main(name: Option[String], limit: Option[Int], uid: Option[String],
login: Option[String], debug: Option[Boolean]) {
if (Seq(name, uid, login).flatten.isEmpty) {
println("You must specify at least one search criteria")
System.exit(1)
}
val effDebug = debug.getOrElse(false)
val futUsrLDAPA = Future { ldapA.findUsr(name, limit, uid, login) }
val futUsrLDAPB = Future { ldapB.findUsr(name, limit, uid, login) }
val futUsrDB = Future { dbase.findUsr(name, limit, uid, login) }
futUsrLDAPA.onComplete(printResult("--- LDAP A ---", debug = effDebug))
futUsrLDAPB.onComplete(printResult("--- LDAP B ---", debug = effDebug))
futUsrDB.onComplete(printResult("--- Data Base ---", debug = effDebug))
val res = Await.ready(
Future.sequence(Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB)), Duration.Inf)
}
}
After completion, print the result as a side effect
Example: usrobject usr extends optional.Application {
def main(name: Option[String], limit: Option[Int], uid: Option[String],
login: Option[String], debug: Option[Boolean]) {
if (Seq(name, uid, login).flatten.isEmpty) {
println("You must specify at least one search criteria")
System.exit(1)
}
val effDebug = debug.getOrElse(false)
val futUsrLDAPA = Future { ldapA.findUsr(name, limit, uid, login) }
val futUsrLDAPB = Future { ldapB.findUsr(name, limit, uid, login) }
val futUsrDB = Future { dbase.findUsr(name, limit, uid, login) }
futUsrLDAPA.onComplete(printResult("--- LDAP A ---", debug = effDebug))
futUsrLDAPB.onComplete(printResult("--- LDAP B ---", debug = effDebug))
futUsrDB.onComplete(printResult("--- Data Base ---", debug = effDebug))
val res = Await.ready(
Future.sequence(Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB)), Duration.Inf)
}
}
Wait until the three queries finish
Example: usrobject usr extends optional.Application {
def main(name: Option[String], limit: Option[Int], uid: Option[String],
login: Option[String], debug: Option[Boolean]) {
if (Seq(name, uid, login).flatten.isEmpty) {
println("You must specify at least one search criteria")
System.exit(1)
}
val effDebug = debug.getOrElse(false)
val futUsrLDAPA = Future { ldapA.findUsr(name, limit, uid, login) }
val futUsrLDAPB = Future { ldapB.findUsr(name, limit, uid, login) }
val futUsrDB = Future { dbase.findUsr(name, limit, uid, login) }
futUsrLDAPA.onComplete(printResult("--- LDAP A ---", debug = effDebug))
futUsrLDAPB.onComplete(printResult("--- LDAP B ---", debug = effDebug))
futUsrDB.onComplete(printResult("--- Data Base ---", debug = effDebug))
val res = Await.ready(
Future.sequence(Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB)), Duration.Inf)
}
}
Has a bug!
Example: completing futures
futRes.onComplete(printResult)
def onComplete[U](f: Try[T] => U): Unit
Try[T] = Success(t: T) | Failure(e: Throwable)
futRes.onComplete(printResult)
def onComplete[U](f: Try[T] => U): Unit
Example: completing futures
Either[Throwable, T]~~
Try[T] = Success(t: T) | Failure(e: Throwable)
futRes.onComplete(printResult)
def onComplete[U](f: Try[T] => U): Unit
When this Future finishes, either correctly (Success) or with an exception (Failure), apply the function f which knows how to handle both cases to compute some side effect
Example: completing futures
Either[Throwable, T]Try[T] = Success(t: T) | Failure(e: Throwable)
~~
futRes.onComplete(printResult)
def onComplete[U](f: Try[T] => U): Unit
In this case the side effect is just printing the result to the console.
Example: completing futures
Either[Throwable, T]~~
Try[T] = Success(t: T) | Failure(e: Throwable)
Future.sequence(
Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB))
def sequence[A, M[_]](
in: M[Future[A]]): Future[M[A]]
Example: sequencing futures
Future.sequence(
Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB))
def sequence[A, M[_]](
in: M[Future[A]]): Future[M[A]]
Convert some container M of Future[A] in a Future M[A].
Example: sequencing futures
Future.sequence(
Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB))
def sequence[A, M[_]](
in: M[Future[A]]): Future[M[A]]
Convert some container M of Future[A] in a Future M[A].
Example: sequencing futuresM[X] <: TraversableOnce[X]
Future.sequence(
Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB))
def sequence[A, M[_]](
in: M[Future[A]]): Future[M[A]]
Convert some container M of Future[A] in a Future M[A].Useful to reduce many Futures into a single Future.
Example: sequencing futures
Future.sequence(
Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB))
def sequence[A, M[_]](
in: M[Future[A]]): Future[M[A]]
Convert some container M of Future[A] in a Future M[A].Useful to reduce many Futures into a single Future.In this case we reduce three independent future results to a single future result with a Seq of the individual results.
Example: sequencing futures
implicit class TryHarder[T](val t: Try[T]) extends AnyVal {
def toEither: Either[Throwable, T] = t match { case Success(s) => Right(s) case Failure(f) => Left(f) }
def fold[U](success: T => U, failure: Throwable => U): U = toEither.fold(failure, success)}
Example: folding on Try
implicit class TryHarder[T](val t: Try[T]) extends AnyVal {
def toEither: Either[Throwable, T] = t match { case Success(s) => Right(s) case Failure(f) => Left(f) }
def fold[U](success: T => U, failure: Throwable => U): U = toEither.fold(failure, success)}
Example: folding on Try
Try is pretty much a specialized Either
implicit class TryHarder[T](val t: Try[T]) extends AnyVal {
def toEither: Either[Throwable, T] = t match { case Success(s) => Right(s) case Failure(f) => Left(f) }
def fold[U](success: T => U, failure: Throwable => U): U = toEither.fold(failure, success)}
Example: folding on Try
Try is pretty much a specialized Either
Example: fixing the bugobject usr extends optional.Application {
def main(name: Option[String], limit: Option[Int], uid: Option[String],
login: Option[String], debug: Option[Boolean]) {
if (Seq(name, uid, login).flatten.isEmpty) {
println("You must specify at least one search criteria")
System.exit(1)
}
val effDebug = debug.getOrElse(false)
val futUsrLDAPA = Future { ldapA.findUsr(name, limit, uid, login) }
val futUsrLDAPB = Future { ldapB.findUsr(name, limit, uid, login) }
val futUsrDB = Future { dbase.findUsr(name, limit, uid, login) }
futUsrLDAPA.onComplete(printResult("--- LDAP A ---", debug = effDebug))
futUsrLDAPB.onComplete(printResult("--- LDAP B ---", debug = effDebug))
futUsrDB.onComplete(printResult("--- Data Base ---", debug = effDebug))
val res = Await.ready(
Future.sequence(Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB)), Duration.Inf)
}
}
The problem is that onComplete executes after the Future is completed. Await only awaits to the completion, not the execution of onComplete and the program can terminate “too soon”.
implicit class FutureOps[T](val f: Future[T])
extends AnyVal {
def continue[S]( cont: Try[T] => S): Future[S] = {
val p = Promise[S]() f.onComplete { tt => p.complete(Try(cont(tt))) } p.future
}}
Example: fixing the bug
implicit class FutureOps[T](val f: Future[T])
extends AnyVal {
def continue[S]( cont: Try[T] => S): Future[S] = {
val p = Promise[S]() f.onComplete { tt => p.complete(Try(cont(tt))) } p.future
}}
Example: fixing the bug
Like onComplete but returning another Future
Example: now works :)object usr extends optional.Application {
def main(name: Option[String], limit: Option[Int], uid: Option[String],
login: Option[String], debug: Option[Boolean]) {
if (Seq(name, uid, login).flatten.isEmpty) {
println("You must specify at least one search criteria")
System.exit(1)
}
val effDebug = debug.getOrElse(false)
val futUsrLDAPA = Future { ldapA.findUsr(name, limit, uid, login) }
val futUsrLDAPB = Future { ldapB.findUsr(name, limit, uid, login) }
val futUsrDB = Future { dbase.findUsr(name, limit, uid, login) }
val endLDAPA = futUsrLDAPA.continue(printResult("--- LDAP A ---"))
val endLDAPB = futUsrLDAPB.continue(printResult("--- LDAP B ---"))
val endDB = futUsrDB.continue(printResult("--- Data Base ---"))
val res = Await.ready(
Future.sequence(Seq(endLDAPA, endLDAPB, endDB)), Duration.Inf)
}
}
github.com/gerferra/crappy-optional
FIN
sub
mini-demo?
FIN