# Expressions

Jean-Rémi Desjardins

an alternative to for-comprehensions

Scala by the Bay 2015

def getPhone: Future[Phone]
def getSpamScore: Future[SpamScore]
for {p <- getPhone
s <- getSpamScore} yield render(p, a, s)

What's wrong?

phone: Future[Phone]
spamScore: Future[SpamScore]
for {p <- phone
s <- spamScore} yield render(p, a, s)

phone: Future[Phone] // 1 second
spamScore: Future[SpamScore] // fail after 10 milliseconds
for {p <- phone
s <- spamScore} yield render(p, a, s) // fails after 2 seconds

The problem

phone: Future[Phone] // 1 second
spamScore: Future[SpamScore] // fail after 10 milliseconds
Applicative[Future].apply3(a,b,c)(combine)

A Solution

through scalaz

phone: Future[Phone] // 1 second
getSpamScore(phone: Phone): Future[SpamScore] // fail after 10 milliseconds
for {
p <- phone
spamScore <- getSpamScore(phone)
} yield render(p, address, spamScore) // fails after 2 seconds

More Problems

phone: Future[Phone] // 1 second
getSpamScore(phone: Phone): Future[SpamScore] // fail after 10 milliseconds
phone.map { p =>
}
} // fails after 10 milliseconds

A Solution

phone: Future[Phone] // 1 second
getSpamScore(phone: Phone): Future[SpamScore] // fail after 10 milliseconds
Expression{
val p = extract(phone)
} // fails after 10 milliseconds

A Better Solution

through Expressions

phone: Future[Phone] // 1 second
getSpamScore(phone: Phone): Future[SpamScore] // fail after 10 milliseconds
Expression{
val p = extract(phone)
val spamScore = extract(getSpamScore(p))
} // fails after 10 milliseconds

Or if you prefer

## Why exactly does this not fail fast?

val a = Future { sleep(1.second); 5 }
val b = Future { sleep(5.seconds); 10}
val c = Future { sleep(3.seconds); fail()}

for {aa <- a
bb <- b
cc <- c} yield combine(a, b, c)

See my talk at PNWScala 2014

## Desugared

val a = Future { sleep(1.second); 5 }
val b = Future { sleep(5.seconds); 10}
val c = Future { sleep(3.seconds); fail()}

a.flatMap { aa =>
b.flatMap { bb =>
c.map { cc =>
combine(aa, bb, cc)
}
}
}

# flatMap()

trait Future[+A] {
def flatMap(f: A => Future[B]): Future[B]
}

# zip()

trait Future[+A] {
def flatMap(f: A => Future[B]): Future[B]
def zip[B](fb: Future[B]): Future[(A,B)]
}

## Desugared

val a = Future { sleep(1.second); 5 }
val b = Future { sleep(5.seconds); 10}
val c = Future { sleep(3.seconds); fail()}

a.zip(b).zip(c) { case ((aa, bb), cc) =>
combine(aa, bb, cc)
}

val a = Future { sleep(1.second); 5 }
val b = Future { sleep(5.seconds); 10}
val c = Future { sleep(3.seconds); fail()}

async { combine(await(a), await(b), await(c)) }

A notation for all the things

Expressions

# Features

• Uses the least powerful interface

• ​Futures can fail-fast

• supports Validation

• Plays well with if and match statements

• Unified notation for all abstractions

• Customizable

# Examples

## Failing fast

for {aa <- a
bb <- b
cc <- c} yield combine(a, b, c)
Expression { combine(a,b,c) }
val a: Future[A] = wait(5)
val b: Future[B] = fail(1)
val c: Future[C] = wait(3)

wait(5)

fail(1)

## Interacting with if

for {aa <- a
bb <- b
cc <- c} yield if (aa == something) polish(bb) else polish(cc)
(for (aa <- a) yield
if (aa == something) for (bb <- b) yield polish(bb)
else for (cc <- c) yield polish(cc)).flatMap(identity)
val a: Future[A] = wait(1)
val b: Future[B] = wait(5)
val c: Future[C] = wait(2)
Expression { if(extract(a) == something) polish(b) else polish(c) }

if (aa == something) => wait(5)

if (aa != something) => wait(5)

if (aa == something) => wait(5)

if (aa != something) => wait(2)

if (aa == something) => wait(5)

if (aa == something) => wait(5)

## Supports all the abstractions

val a: Option[A]
val b: Option[B]
val c: Option[C]
Expression { if(extract(a) == something) polish(b) else polish(c) }
val a: Err \/ A
val b: Err \/ B
val c: Err \/ C
val a: Writer[A]
val b: Writer[B]
val c: Writer[C]
val a: IO[A]
val b: IO[B]
val c: IO[C]
val a: List[A]
val b: List[B]
val c: List[C]

# Supports Validation

in theory...

val first = Validation.success(5)
val second = Validation.success(4)
def getThird(a: Int): ValidationNel[String, Int] = Validation.failureNel("too big")
val fourth: ValidationNel[String, Int] = Validation.failureNel("too small")
val result = Expression[({type l[a] = Validation[NonEmptyList[String], a]})#l, Int] {
val one = extract2(first)
one + extract2(second) + extract2(getThird(one)) + extract2(fourth)
result ==== Failure(NonEmptyList("too big", "too small"))

# Uses Scalaz

trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}

trait Apply[F[_]] extends Functor[F] {
// looks like something we've seen before?
def apply2[A,B,C](fa: F[A], fb: F[B])(f: (A,B) => C): F[C]
//derived
def map[A,B](fa: F[A])(f: A => B): F[B] = derived
}

trait Applicative[F[_]] extends Apply[F] {
def point[A](a: A): F[A]
}

// looks like something we've seen before?
def bind[A,B](fa: F[A])(f: A => F[B]): F[B]
//derived
def apply2[A,B,C](fa: F[A], fb: F[B])(f: (A,B) => C): F[C] = derived
}

(but doesn't need too...)

## Simple code transformation

Expression { foo(extract(a), b, extract(c) }
Applicative[_].apply2(a,c)(foo(_,b,_))
val a: Future[String]
val b: String
val c: Future[String]

def foo(one: String, two: String, three: String)

## Importance of using the least powerful interface

Functor

Applicative

### Flexibility offered to implementation

Functor

Applicative

Expression { foo(extract(bar(extract(a))), b, extract(c)) }
val a: Future[String]
val b: String
val c: Future[String]

def foo(one: String, two: String, three: String)
def bar(g: String): Future[String]

## if statement

Expression { if (extract(a)) extract(b) else extract(c) }
val a: Future[String]
val b: String
val c: Future[String]

## match statement

Expression {
extract(a) match {
case 1 => extract(b)
case 2 => extract(c) + 1
}
}
case 1 => b
case 2 => instance.map(c)(cc => cc + 1)
}
val a: Future[String]
val b: Future[String]
val c: Future[String]

## match statement can get hairy

Expression {
val fooY = extract(foo)
extract(bar) match {
case `fooY` => extract(b)
case 2 => extract(c) + 1
}
}
val fooY = getFoo
case `x\$2` => b
case 2 => instance.map(c)(cc => cc + 1)
}
val foo: Future[String]
val bar: Future[String]
val b: Future[String]
val c: Future[String]

## match statement can get REALLY hairy

Expression {
val fooY = extract(foo)
val bizY = extract(biz)
extract(bar) match {
case `fooY` => extract(a)
case `bizY` => extract(c)
case 2 => extract(c) + 1
}
}
{
val fooY = foo
val bizY = biz
case `x\$2` => a
case _ => Monad[_].bind(biz){ x\$3 => x\$1 match {
case `x\$3` => b
case 2 => instance.map(c)(cc => cc + 1)
}
}
}

# Similar Projects

• Effectful

• ​Alternative to for-comprehension and/or generalization of Async/Await

• Does not use the least powerful interface (cannot fail-fast)

• Scala Workflow

• Many features (all Expressions features + context manipulation)

• Uses untyped macros

• Reimplements parts of scalac including scoping which leads to minimal coverage of the language

• Only works with Scala Futures

• Does not fail-fast

# Limitations

## Known to work (somewhat)

• function applications
• if-else statement
• function currying
• string interpolation
• blocks
• basic match statements

## Know limitations

• pattern matching in value definitions

# When to use them

## Write Async code

val response: Future[HTML] = Expression {
val phone = extract(lookupPhone(phoneString))
val rep = extract(lookupReputation(phone))
}

## Large blocks of code within a Monad

val response: Option[HTML] = Expression {
val firstFruit = json.fruits(0)
extract(firstFruit.as[String]) match {
case "apple" => "good"
case "peach" =>
else "good"
}
}

# Not a replacement for for-comprehensions

} yield ((t2 - t1).milliseconds, a)

# Future work

• Use Scala Meta
• Improve ScalaCheck function generation
• Implement Context manipulation
• Support nested abstractions
• Fix known limitations
• Generalize tests

# Context Manipulation

a: Writer[String, Int] = 8.set("This is a magic value")
b: Writer[String, Int] = random().set("I am a random value")
c: Writer[String, String] = "Hello World".set("Hello World...")
Expression {
val div = if (b == 0) {
ctx :++> "We avoided a division by zero, yay!"
5
} else a / b
c * div
}