Expressions
Jean-Rémi Desjardins
Scala By The Bay 2015
an alternative to for-comprehensions
def getPhone: Future[Phone]
def getAddress: Future[Address]
def getSpamScore: Future[SpamScore]
for {p <- getPhone
a <- getAddress
s <- getSpamScore} yield render(p, a, s)
phone: Future[Phone]
address: Future[Address]
spamScore: Future[SpamScore]
for {p <- phone
a <- address
s <- spamScore} yield render(p, a, s)
phone: Future[Phone] // 1 second
address: Future[Address] // fail after 10 milliseconds
spamScore: Future[SpamScore] // 2 seconds
for {p <- phone
a <- address
s <- spamScore} yield render(p, a, s)
phone: Future[Phone] // 1 second
address: Future[Address] // fail after 10 milliseconds
spamScore: Future[SpamScore] // 2 seconds
Applicative[Future].apply3(a,b,c)(combine)
bind or flatMap cannot "fail-fast"
History
See my talk at PNWScala
Bind cannot "fail-fast"
trait Monad[F[_]] {
def bind[A,B](fa: F[A])(f : a => F[B]): F[B]
}
Expressions are an alternative to for-comprehensions that support failing fast among a few other things
Examples
Fails fast
val a: Future[A]
val b: Future[B]
val c: Future[C]
Fails fast
for {aa <- a
bb <- b
cc <- c} yield combine(a, b, c)
val a: Future[A]
val b: Future[B]
val c: Future[C]
Fails fast
for {aa <- a
bb <- b
cc <- c} yield combine(a, b, c)
Expression { combine(a,b,c) }
val a: Future[A]
val b: Future[B]
val c: Future[C]
Plays well with if and match
val a: Future[A]
val b: Future[B]
val c: Future[C]
Plays well with if and match
for {aa <- a
bb <- b
cc <- c} yield if (aa == something) polish(bb) else polish(cc)
val a: Future[A]
val b: Future[B]
val c: Future[C]
Plays well with if and match
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]
val b: Future[B]
val c: Future[C]
Plays well with if and match
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]
val b: Future[B]
val c: Future[C]
Expression { if(extract(a) == something) polish(b) else polish(c) }
Not a replacement for for-comprehensions
def time[A](task: Task[A]): Task[(Duration, A)] = for {
t1 <- Task.delay(System.currentTimeMillis)
a <- task
t2 <- Task.delay(System.currentTimeMillis)
} yield ((t2 - t1).milliseconds, a)
Example from Remotely
When to use them
Write Async code
val response: Future[HTML] = Expression {
val phone = extract(lookupPhone(phoneString))
val address = extract(lookupAddress(phone))
val rep = extract(lookupReputation(phone))
renderPage(phone, address, rep)
}
But what about a single unified notation
-
That supports:
-
Monad
-
Applicative Functor
-
Functor
-
conceivably others
-
Wouldn't that be nice Professor? One single elegant equation to explain everything?
Not only does that reduce cognitive overload, but it is more powerful
- Monad cannot support fail-fast for Futures
- The more expressive an abstraction, the less flexible it is in it's implementation
-
Our notation should use the least powerful abstraction that is required
- This allows maximum flexibility to implement behavior of combinators
Why Monad cannot fail fast
trait Monad[F[_]] {
def bind[A,B](m: F[T], f: A => F[B]): F[B]
}
What does it look like
val phoneString: String = ???
val lookupPhone: String => Future[Phone] = ???
val lookupAddress: Phone => Future[Address] = ???
val lookupReputation: Phone => Future[Score] = ???
val renderPage: (Phone, Address, Int) => HTML = ???
val response: Future[HTML] = Expr {
val phone = lookupPhone(phoneString)
val address = lookupAddress(phone)
val rep = lookupReputation(phone)
renderPage(phone, address, rep)
}
Uses Scala implicit resolution to mirror Idris behavior but can be made explicit
import com.github.jedeash.Expr
import com.github.jedesah.Expr.extract
val phoneString: String = ???
val lookupPhone: String => Future[Phone] = ???
val lookupAddress: Phone => Future[Address] = ???
val lookupReputation: Phone => Future[Score] = ???
val renderPage: (Phone, Address, Int) => HTML = ???
val response: Future[HTML] = Expr {
val phone = extract(lookupPhone(phoneString))
val address = lookupAddress(phone)
val rep = lookupReputation(phone)
renderPage(phone, extract(address), extract(rep))
}
Explicit
Implicit
import com.github.jedeash.Expr
import com.github.jedesah.Expr.auto.extract
val phoneString: String = ???
val lookupPhone: String => Future[Phone] = ???
val lookupAddress: Phone => Future[Address] = ???
val lookupReputation: Phone => Future[Score] = ???
val renderPage: (Phone, Address, Int) => HTML = ???
val response: Future[HTML] = Expr {
val phone = lookupPhone(phoneString)
val address = lookupAddress(phone)
val rep = lookupReputation(phone)
renderPage(phone, address, rep)
}
Prior Art
-
Effectful*
-
Replacement for for-comprehension
-
Generalizes Async/Await
-
Only supports Monad
-
-
Scala Workflow*
-
Supports everything I do and more
-
Uses untyped macros
-
Reimplements parts of scalac including scoping which leads to minimal coverage of the language
-
*https://github.com/pelotom/effectful
*https://github.com/aztek/scala-workflow/
Scala Computation Expressions
- https://github.com/jedesah/computation-expressions
- Releasing 0.1.0 today (version and date may change)
Thought: Towards automatic code parallelization
- One of the promises of purely functional programming
- Tried by Facebook with FXL
- Computation Expressions do a lot to bring us there
Thank you
Questions, comments?
Computation Expressions - Scala By the Bay 2015
By Jean-Rémi Desjardins
Computation Expressions - Scala By the Bay 2015
- 1,381