Practical Examples
Coin Flip Game
A complete example combining multiple effects:
import in.rcard.yaes.Random.*
import in.rcard.yaes.Output.*
import in.rcard.yaes.Input.*
import in.rcard.yaes.Raise.*
def coinFlipGame(using Random, Output, Input, Raise[String]): String = {
Output.printLn("Welcome to the Coin Flip Game!")
Output.printLn("Guess: heads or tails?")
val guess = Input.readLn()
val flip = if (Random.nextBoolean) "heads" else "tails"
Output.printLn(s"The coin landed on: $flip")
if (guess.toLowerCase == flip) {
Output.printLn("You won!")
"win"
} else {
Output.printLn("You lost!")
"lose"
}
}
// Run the game
val result = Raise.option {
Output.run {
Input.run {
Random.run {
coinFlipGame
}
}
}
}
File Processing with Resource Management
import in.rcard.yaes.Resource.*
import in.rcard.yaes.IO.*
import in.rcard.yaes.Raise.*
import java.io.{FileInputStream, FileOutputStream}
def processFiles(inputPath: String, outputPath: String)(using Resource, IO): Unit = {
val input = Resource.acquire(new FileInputStream(inputPath))
val output = Resource.acquire(new FileOutputStream(outputPath))
Resource.ensuring {
println("File processing completed")
}
// Process files...
val buffer = new Array[Byte](1024)
var bytesRead = input.read(buffer)
while (bytesRead != -1) {
output.write(buffer, 0, bytesRead)
bytesRead = input.read(buffer)
}
}
// Run with automatic cleanup
Resource.run {
IO.run {
processFiles("input.txt", "output.txt")
}
}
Concurrent Web Scraping
import in.rcard.yaes.Async.*
import in.rcard.yaes.IO.*
import in.rcard.yaes.Log.*
def fetchUrl(url: String)(using IO, Log): String = {
val logger = Log.getLogger("WebScraper")
logger.info(s"Fetching: $url")
// Simulate HTTP request
Thread.sleep(1000)
s"Content from $url"
}
def scrapeUrls(urls: List[String])(using Async, IO, Log): List[String] = {
val fibers = urls.map { url =>
Async.fork(s"fetch-$url") {
fetchUrl(url)
}
}
fibers.map(_.join())
}
// Run concurrent scraping
val results = Log.run {
IO.run {
Async.run {
scrapeUrls(List(
"https://example.com",
"https://scala-lang.org",
"https://github.com"
))
}
}
}
Configuration Loading
import in.rcard.yaes.System.*
import in.rcard.yaes.Raise.*
import in.rcard.yaes.Log.*
case class AppConfig(
host: String,
port: Int,
dbUrl: String,
logLevel: String
)
def loadConfig(using System, Raise[String], Log): AppConfig = {
val logger = Log.getLogger("Config")
logger.info("Loading application configuration")
val host = System.env[String]("HOST", "localhost")
val port = System.env[Int]("PORT").getOrElse {
Raise.raise("PORT environment variable is required")
}
val dbUrl = System.property[String]("db.url").getOrElse {
Raise.raise("db.url system property is required")
}
val logLevel = System.env[String]("LOG_LEVEL", "INFO")
AppConfig(host, port, dbUrl, logLevel)
}
// Load configuration
val config = Raise.either {
Log.run {
System.run {
loadConfig
}
}
}
Error Handling Pipeline
import in.rcard.yaes.Raise.*
import in.rcard.yaes.IO.*
sealed trait ValidationError
case object InvalidEmail extends ValidationError
case object InvalidAge extends ValidationError
case class DatabaseError(msg: String) extends ValidationError
case class User(email: String, age: Int)
def validateEmail(email: String)(using Raise[ValidationError]): String = {
if (email.contains("@")) email
else Raise.raise(InvalidEmail)
}
def validateAge(age: Int)(using Raise[ValidationError]): Int = {
if (age >= 0 && age <= 120) age
else Raise.raise(InvalidAge)
}
def saveUser(user: User)(using IO, Raise[ValidationError]): Long = {
// Simulate database operation that might fail
if (user.email.endsWith("@spam.com")) {
Raise.raise(DatabaseError("Spam domain not allowed"))
}
42L // User ID
}
def createUser(email: String, age: Int)(using IO, Raise[ValidationError]): Long = {
val validEmail = validateEmail(email)
val validAge = validateAge(age)
val user = User(validEmail, validAge)
saveUser(user)
}
// Handle all errors
val result = Raise.either {
IO.run {
createUser("john@example.com", 25)
}
}
result match {
case Right(userId) => println(s"User created with ID: $userId")
case Left(InvalidEmail) => println("Invalid email format")
case Left(InvalidAge) => println("Invalid age")
case Left(DatabaseError(msg)) => println(s"Database error: $msg")
}
Flow Data Processing
Using Flow from the yaes-data module for stream processing:
import in.rcard.yaes.Flow
import in.rcard.yaes.Random.*
import in.rcard.yaes.Output.*
import in.rcard.yaes.Log.*
case class SensorReading(id: Int, temperature: Double, humidity: Double)
def processSensorData(readings: List[SensorReading])(using Log, Output): List[String] = {
val logger = Log.getLogger("SensorProcessor")
val results = scala.collection.mutable.ArrayBuffer[String]()
readings.asFlow()
.onStart {
logger.info("Starting sensor data processing")
Output.printLn("Processing sensor readings...")
}
.filter(_.temperature > 25.0) // Hot readings only
.filter(_.humidity < 60.0) // Not too humid
.map { reading =>
s"Alert: Sensor ${reading.id} - Temp: ${reading.temperature}°C, Humidity: ${reading.humidity}%"
}
.onEach { alert =>
Output.printLn(alert)
}
.take(5) // Limit alerts
.collect { alert =>
results += alert
}
logger.info(s"Generated ${results.length} alerts")
results.toList
}
// Generate sample data and process
def generateSensorReadings(using Random): List[SensorReading] = {
(1 to 20).map { id =>
SensorReading(
id = id,
temperature = Random.nextDouble * 40.0, // 0-40°C
humidity = Random.nextDouble * 100.0 // 0-100%
)
}.toList
}
val alerts = Log.run {
Output.run {
Random.run {
val readings = generateSensorReadings
processSensorData(readings)
}
}
}
println(s"Total alerts generated: ${alerts.length}")
Real-time Data Pipeline
Combining Flow with async processing:
import in.rcard.yaes.Flow
import in.rcard.yaes.Async.*
import in.rcard.yaes.Log.*
case class LogEntry(timestamp: Long, level: String, message: String)
def processLogStream(logs: List[LogEntry])(using Async, Log): Map[String, Int] = {
val logger = Log.getLogger("LogProcessor")
val levelCounts = scala.collection.mutable.Map[String, Int]()
// Process logs asynchronously using Flow
val processingFiber = Async.fork("log-processor") {
logs.asFlow()
.onStart {
logger.info("Starting log analysis")
}
.filter(_.level != "TRACE") // Skip trace logs
.transform { log =>
// Simulate async processing time
Async.delay(10.millis)
Flow.emit(log)
}
.onEach { log =>
logger.debug(s"Processing ${log.level} log: ${log.message}")
}
.collect { log =>
levelCounts.updateWith(log.level) {
case Some(count) => Some(count + 1)
case None => Some(1)
}
}
}
// Wait for processing to complete
processingFiber.join()
logger.info(s"Processed logs by level: $levelCounts")
levelCounts.toMap
}
// Usage
val sampleLogs = List(
LogEntry(System.currentTimeMillis(), "INFO", "Application started"),
LogEntry(System.currentTimeMillis(), "DEBUG", "Loading configuration"),
LogEntry(System.currentTimeMillis(), "WARN", "Deprecated API used"),
LogEntry(System.currentTimeMillis(), "ERROR", "Database connection failed"),
LogEntry(System.currentTimeMillis(), "INFO", "Retrying connection"),
LogEntry(System.currentTimeMillis(), "TRACE", "Method entered")
)
val results = Log.run {
Async.run {
processLogStream(sampleLogs)
}
}