an excuse to try, either, folding, and future. sequence

Post on 24-May-2015

308 Views

Category:

Software

0 Downloads

Preview:

Click to see full reader

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

top related