def printMessages(threadName: String): Unit =
for i <- 1 to 5 do
println(s"$threadName: Message $i")
Thread.sleep(1000)
@main def threads =
val thread1 = new Thread(() => printMessages("Thread 1"))
val thread2 = new Thread(() => printMessages("Thread 2"))
thread1.start()
thread2.start()
trait Api:
def get(id: Int): Option[String]
class Service(getApi: Future[Option[Api]]):
def calculate: Future[Option[String]] =
getApi.map{
apiOpt =>
apiOpt.flatMap{
api => api.get(1)
}
}
trait Api:
def get(id: Int): Future[Option[String] ]
class Service(getApi: Future[Option[Api]]):
def calculate: Future[Option[String]] =
getApi.flatMap{
apiOpt =>
apiOpt.map{
api => api.get(1)
}.getOrElse(Future.successful(None))
}
It leaks everywhere and you're not really interested in it for your logic
We still can have future spawned outside our control.
There are different monad abstractions to deal with that, but they are far from trivial
We want to have a verifiable way that nothing escapes the current scope
We want cancelling, make sure everything is handled properly.
This essentially creates a tree structure.
It is already widely used in Cats/ZIO
Functional frameworks require monads and ways of composing them.
Higher and more complicated abstractions.
var running = true;
while (running) {
var time = await window.animationFrame;
context.clearRect(0, 0, 500, 500);
context.fillRect(time % 450, 20, 50, 50);
}
concurrency - developer-friendly structured concurrency
error management: retries, timeouts, a safe approach to error propagation, safe resource management
scheduling & timers
resiliency: circuit breakers, bulkheads, rate limiters, backpressure
//> using dep "com.softwaremill.ox::core:0.2.2"
import ox.*
@main def main =
supervised:
val hello = forkUser:
print("Hello")
val world = forkUser:
hello.join()
println(", world!")
world.join()
- cross-platform high-level asynchronous code
- direct-style Scala and structured concurrency
- featureful primitives and expose a simple direct-style API.
//> using dep "ch.epfl.lamp::gears::0.2.0"
import gears.async.*
import gears.async.default.given
@main def main() =
Async.blocking:
val hello = Future:
print("Hello")
val world = Future:
hello.await
println(", world!")
world.await
Most existing Scala frameworks do represent structured concurrency, but not direct style.
They might be considered more difficult for beginners.
object EasyRacerClient extends ZIOAppDefault:
def scenario1(scenarioUrl: Int => String) =
defer:
val url = scenarioUrl(1)
val req = Client.request(Request.get(url))
val winner = req.race(req).run
winner.body.asString.run
import cats.effect.IO
import cats.effect.cps._
import scala.concurrent.duration._
val io = IO.sleep(50.millis).as(1)
val program: IO[Int] = async[IO] { io.await + io.await }
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
function resolveAfter2() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('resolved');
}, 2000);
});
}
async function asyncCall() {
console.log('calling');
const result = await resolveAfter2();
console.log(result);
}
use mini_redis::{client, Result};
#[tokio::main]
async fn main() -> Result<()> {
// Open a connection to the mini-redis address.
let mut client = client::connect("127.0.0.1:6379").await?;
// Set the key "hello" with value "world"
client.set("hello", "world".into()).await?;
// Get key "hello"
let result = client.get("hello").await?;
println!("got value from the server; result={:?}", result);
Ok(())
}
The features
Both Ox and Gear are direct only and focus on making that experience work for users.
Unlike monadic approach, there are no longer descriptions of the program, but rather the program itself.
I want to run parallel computations to make my code faster
import ox.*
import scala.concurrent.duration.*
def computation(): Int =
sleep(2.seconds)
2
@main def multi =
val result: (Int, Int) = par(computation(), computation())
def computations = Seq.fill(20)(() => computation())
val resultMap: Seq[Unit] =
computations.mapPar(5)(str => println(str()))
val resultForeach: Unit =
computations.foreachPar(5)(str => println(str()))
import gears.async.*
import gears.async.default.given
import scala.concurrent.duration._
def computation(using Async.Spawn) = Future:
AsyncOperations.sleep(2.second)
println("Hello")
@main def multi() =
Async.blocking:
val computations: Seq[() => Future[Unit]] =
Seq.fill(20)(() => computation)
val resMap: Seq[Unit] =
computations.map(fut => fut()).awaitAll
val resGrouped: Unit =
computations.grouped(5).foreach:
futs => futs.map(fut => fut()).awaitAll
I want to get a service or from the database whichever is faster
import ox.*
import scala.concurrent.duration._
@main def racing =
def service: String =
sleep(2.seconds)
"Hello service"
def database: String =
sleep(2.seconds)
"Hello database"
println(race(service, database))
import gears.async.*
import gears.async.default.given
import scala.concurrent.duration._
@main def racing() =
Async.blocking:
def service: Future[String] =
Future:
AsyncOperations.sleep(2001)
"Hello service"
def database: Future[String] =
Future:
AsyncOperations.sleep(2001)
"Hello database"
println(Async.race(service, database).await)
I want to cancel or timeout long running request
import ox.*
import scala.concurrent.duration._
import scala.util.Try
@main def timeoutCancel =
def computation: Int =
sleep(2.seconds)
println("stopped")
1
println(Try(timeout(1.second)(computation)))
println(Try(timeout(3.seconds)(computation)))
supervised:
val cancellable = forkCancellable:
while true do
println("tick")
sleep(1.second)
forkUser:
sleep(4.seconds)
cancellable.cancel()
import gears.async.*
import gears.async.default.given
import scala.concurrent.duration._
import scala.util.Try
@main def timeoutCancel() =
Async.blocking:
def computation: Int =
AsyncOperations.sleep(2.seconds)
println("stopped")
1
println(Try(withTimeout(1.seconds)(computation)))
println(Try(withTimeout(3.seconds)(computation)))
val future = Future:
while true do
println("tock")
AsyncOperations.sleep(1.second)
Future:
AsyncOperations.sleep(4000)
future.cancel()
future.await
I want to properly handle any exceptions
@main def exceptionsOx() =
supervised:
def computation(withException: Option[String]): Int =
sleep(2.seconds)
withException match
case None => 1
case Some(value) =>
throw new Exception(value)
val fork1 = fork:
computation(withException = None)
val fork2 = fork:
computation(withException = Some("Oh no!"))
val fork3 = fork:
computation(withException = Some("Oh well.."))
println(fork1.join())
@main def exceptionsOxBonus() =
val res: Either[Throwable, Int] =
supervisedError(ox.EitherMode[Throwable]()):
def computation(withException: Option[String]) =
sleep(2.seconds)
withException match
case None => Right(1)
case Some(value) =>
Left(Exception(value))
val fork1 = forkError:
computation(withException = None)
val fork2 = forkError:
computation(withException = Some("Oh no!"))
val fork3 = forkError:
computation(withException = Some("Oh well.."))
println(fork1.join())
println(fork2.join())
println(fork3.join())
Right(1)
println("Hello!")
println(res)
@main def exceptionsGears() =
Async.blocking:
def computation(withException: Option[String]): Int =
AsyncOperations.sleep(2.seconds)
withException match
case None => 1
case Some(value) =>
throw new Exception(value)
val future1 = Future:
computation(withException = None)
val future2 = Future:
computation(withException = Some("Oh no!"))
val future3 = Future:
computation(withException = Some("Oh well.."))
future1.await
future2.awaitResult
future3.await
I want retry anything that fails
import ox.resilience._
def request(): Int =
sleep(1.second)
if Random.nextBoolean() then 100
else
println("Ups!")
throw new Exception("Bad!")
@main def retryOx() =
val policy: RetryPolicy[Throwable, Int] =
RetryPolicy.backoff(3, 100.millis, 5.minutes, Jitter.Equal)
println(retry(policy)(request()))
println(
retryEither(policy)(
Try(request()).toEither
)
)
println(
retryWithErrorMode(UnionMode[Throwable])(policy)(
Try(request()) match
case Failure(exception) => exception
case Success(value) => value
)
)
@main def retryGears() =
def request()(using Async): Int =
AsyncOperations.sleep(1000)
if Random.nextBoolean() then 100
else
println("Ups!")
throw new Exception("Bad!")
Async.blocking:
val result = Retry.untilSuccess
.withMaximumFailures(5)
.withDelay(
Delay.backoff(
maximum = 1.minute,
starting = 1.second,
jitter = Jitter.full
)
)(request())
println(result)
I want communicate some values between threads
import ox.channels.Channel
@main def channelsOx() =
val channel = Channel.buffered[String](5)
supervised:
val sender = forkUser:
sleep(1.second)
c2.send("Hello!")
sleep(3.second)
c2.send("World!")
val receiver = forkUser:
sleep(1.second)
println(c2.receive())
sleep(1.second)
println(c2.receive())
sender.join()
receiver.join()
import ox.channels.Channel
@main def channelsGears() =
val channel = BufferedChannel[String](5)
Async.blocking:
val sender = Future:
AsyncOperations.sleep(1.second)
channel.send("Hello!")
AsyncOperations.sleep(3.second)
channel.send("World!")
val receiver = Future:
AsyncOperations.sleep(1.second)
println(channel.read()) // Either[Closed, T]
AsyncOperations.sleep(1.second)
println(channel.read()) // Either[Closed, T]
sender.await
receiver.await
Summary
Vs
Vs
Vs
Vs
Vs
Vs
Vs