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

101
An excuse to Try, Either, folding, and Future. sequence or parsing command line options with crappy-optional @gerferra

Upload: german-ferrari

Post on 24-May-2015

306 views

Category:

Software


0 download

DESCRIPTION

Some example usage of Scala and FP, using as an excuse some code that I wrote to parse command line options.

TRANSCRIPT

Page 1: An excuse to Try, Either, folding, and Future. sequence

An excuse to Try, Either, folding, and Future.

sequence or parsing command line options with

crappy-optional

@gerferra

Page 2: An excuse to Try, Either, folding, and Future. sequence

crappy-optional?

“Don’t worry be crappy” mantra

optional library from DRMacIverand paulp

Page 3: An excuse to Try, Either, folding, and Future. sequence

optional

optional is a command line option parser and library.

Page 4: An excuse to Try, Either, folding, and Future. sequence

optional

YOU WRITE:

object MyAwesomeCommandLineTool extends optional.Application {

// for instance...

def main(count: Option[Int], file: Option[java.io.File],

arg1: String) {

[...]

}

}

Page 5: An excuse to Try, Either, folding, and Future. sequence

optional

THEN YOU DO:

scala MyAwesomeCommandLineTool --count 5 quux

Page 6: An excuse to Try, Either, folding, and Future. sequence

optional

AND YOUR MAIN METHOD WILL BE INVOKED SUCH THAT:

count = Some(5)file = Nonearg1 = quux

Page 7: An excuse to Try, Either, folding, and Future. sequence

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

Page 8: An excuse to Try, Either, folding, and Future. sequence

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

Page 9: An excuse to Try, Either, folding, and Future. sequence

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

Page 10: An excuse to Try, Either, folding, and Future. sequence

optional

HOW IT WORKS:

Page 11: An excuse to Try, Either, folding, and Future. sequence

optional

HOW IT WORKS:

Reflection, man.

Page 12: An excuse to Try, Either, folding, and Future. sequence

optional

HOW IT WORKS:

(Java) Reflection, man.

Page 13: An excuse to Try, Either, folding, and Future. sequence

optional

HOW IT WORKS:

(Java) Reflection, man.

doesn’t work OK with Scala since 2.9 (I guess) ...

used to work

Page 14: An excuse to Try, Either, folding, and Future. sequence

So...

… copy the idea, but use Scala reflection instead …

… and don’t worry and be crappy :) ...

Page 15: An excuse to Try, Either, folding, and Future. sequence

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 ...

}

}

Page 16: An excuse to Try, Either, folding, and Future. sequence

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 */)

}

Page 17: An excuse to Try, Either, folding, and Future. sequence

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

Page 18: An excuse to Try, Either, folding, and Future. sequence

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

Page 19: An excuse to Try, Either, folding, and Future. sequence

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 ...

Page 20: An excuse to Try, Either, folding, and Future. sequence

Step by step: reflecting on instance

val instanceMirror =

ru

.runtimeMirror(getClass.getClassLoader)

.reflect(instance)

Page 21: An excuse to Try, Either, folding, and Future. sequence

Step by step: reflecting on instance

val instanceMirror =

ru

.runtimeMirror(getClass.getClassLoader)

.reflect(instance)

(mystic voice) …You need a mirror to access the reflection…

Page 22: An excuse to Try, Either, folding, and Future. sequence

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 }

Page 23: An excuse to Try, Either, folding, and Future. sequence

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

Page 24: An excuse to Try, Either, folding, and Future. sequence

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

}

Page 25: An excuse to Try, Either, folding, and Future. sequence

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")

Page 26: An excuse to Try, Either, folding, and Future. sequence

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

Page 27: An excuse to Try, Either, folding, and Future. sequence

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

Page 28: An excuse to Try, Either, folding, and Future. sequence

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

Page 29: An excuse to Try, Either, folding, and Future. sequence

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]

Page 30: An excuse to Try, Either, folding, and Future. sequence

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

Page 31: An excuse to Try, Either, folding, and Future. sequence

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

Page 32: An excuse to Try, Either, folding, and Future. sequence

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

Page 33: An excuse to Try, Either, folding, and Future. sequence

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]

Page 34: An excuse to Try, Either, folding, and Future. sequence

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)

Page 35: An excuse to Try, Either, folding, and Future. sequence

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)

Page 36: An excuse to Try, Either, folding, and Future. sequence

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]

Page 37: An excuse to Try, Either, folding, and Future. sequence

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]

Page 38: An excuse to Try, Either, folding, and Future. sequence

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 …)

Page 39: An excuse to Try, Either, folding, and Future. sequence

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]

Page 40: An excuse to Try, Either, folding, and Future. sequence

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

Page 41: An excuse to Try, Either, folding, and Future. sequence

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

Page 42: An excuse to Try, Either, folding, and Future. sequence

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

Page 43: An excuse to Try, Either, folding, and Future. sequence

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]]

Page 44: An excuse to Try, Either, folding, and Future. sequence

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]]

Page 45: An excuse to Try, Either, folding, and Future. sequence

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]

Page 46: An excuse to Try, Either, folding, and Future. sequence

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]]

Page 47: An excuse to Try, Either, folding, and Future. sequence

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"

Page 48: An excuse to Try, Either, folding, and Future. sequence

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: _*)

}

Page 49: An excuse to Try, Either, folding, and Future. sequence

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

Page 50: An excuse to Try, Either, folding, and Future. sequence

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

Page 51: An excuse to Try, Either, folding, and Future. sequence

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

Page 52: An excuse to Try, Either, folding, and Future. sequence

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

Page 53: An excuse to Try, Either, folding, and Future. sequence

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 :-)

Page 54: An excuse to Try, Either, folding, and Future. sequence

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

Page 55: An excuse to Try, Either, folding, and Future. sequence

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]

Page 56: An excuse to Try, Either, folding, and Future. sequence

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)

Page 57: An excuse to Try, Either, folding, and Future. sequence

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)

Page 58: An excuse to Try, Either, folding, and Future. sequence

Step by step: handling ending cases

invocationOrError.fold(println, identity)

Page 59: An excuse to Try, Either, folding, and Future. sequence

Step by step: handling ending cases

invocationOrError.fold(println, identity)

fold[X] ~

Either[A, B] => (A => X) => (B => X) => X

Page 60: An excuse to Try, Either, folding, and Future. sequence

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

Page 61: An excuse to Try, Either, folding, and Future. sequence

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

Page 62: An excuse to Try, Either, folding, and Future. sequence

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

Page 63: An excuse to Try, Either, folding, and Future. sequence

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

Page 64: An excuse to Try, Either, folding, and Future. sequence

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)

}

Page 65: An excuse to Try, Either, folding, and Future. sequence

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

Page 66: An excuse to Try, Either, folding, and Future. sequence

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))

}

}

Page 67: An excuse to Try, Either, folding, and Future. sequence

More ...

assignNames

instantiateUnreferencedParams

instantiateParams

coerce

Page 68: An excuse to Try, Either, folding, and Future. sequence

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! :)

Page 69: An excuse to Try, Either, folding, and Future. sequence

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]

Page 70: An excuse to Try, Either, folding, and Future. sequence

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

Page 71: An excuse to Try, Either, folding, and Future. sequence

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?

Page 72: An excuse to Try, Either, folding, and Future. sequence

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

Page 73: An excuse to Try, Either, folding, and Future. sequence

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

Page 74: An excuse to Try, Either, folding, and Future. sequence

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 ...

Page 75: An excuse to Try, Either, folding, and Future. sequence

Example: usr

Some usage examples:

usr "juan perez"

usr "juan perez" 10

usr --uid jperez

usr --login jperez --limit 10 --debug

Page 76: An excuse to Try, Either, folding, and Future. sequence

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 ...

}}

Page 77: An excuse to Try, Either, folding, and Future. sequence

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)

}

}

Page 78: An excuse to Try, Either, folding, and Future. sequence

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

Page 79: An excuse to Try, Either, folding, and Future. sequence

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

Page 80: An excuse to Try, Either, folding, and Future. sequence

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

Page 81: An excuse to Try, Either, folding, and Future. sequence

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

Page 82: An excuse to Try, Either, folding, and Future. sequence

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

Page 83: An excuse to Try, Either, folding, and Future. sequence

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!

Page 84: An excuse to Try, Either, folding, and Future. sequence

Example: completing futures

futRes.onComplete(printResult)

def onComplete[U](f: Try[T] => U): Unit

Try[T] = Success(t: T) | Failure(e: Throwable)

Page 85: An excuse to Try, Either, folding, and Future. sequence

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)

Page 86: An excuse to Try, Either, folding, and Future. sequence

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)

~~

Page 87: An excuse to Try, Either, folding, and Future. sequence

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)

Page 88: An excuse to Try, Either, folding, and Future. sequence

Future.sequence(

Seq(futUsrLDAPA, futUsrLDAPB, futUsrDB))

def sequence[A, M[_]](

in: M[Future[A]]): Future[M[A]]

Example: sequencing futures

Page 89: An excuse to Try, Either, folding, and Future. sequence

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

Page 90: An excuse to Try, Either, folding, and Future. sequence

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]

Page 91: An excuse to Try, Either, folding, and Future. sequence

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

Page 92: An excuse to Try, Either, folding, and Future. sequence

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

Page 93: An excuse to Try, Either, folding, and Future. sequence

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

Page 94: An excuse to Try, Either, folding, and Future. sequence

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

Page 95: An excuse to Try, Either, folding, and Future. sequence

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

Page 96: An excuse to Try, Either, folding, and Future. sequence

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”.

Page 97: An excuse to Try, Either, folding, and Future. sequence

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

Page 98: An excuse to Try, Either, folding, and Future. sequence

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

Page 99: An excuse to Try, Either, folding, and Future. sequence

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)

}

}

Page 100: An excuse to Try, Either, folding, and Future. sequence

github.com/gerferra/crappy-optional

FIN

sub

Page 101: An excuse to Try, Either, folding, and Future. sequence

mini-demo?

FIN