or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

backported-collections.mdcollection-extensions.mdindex.mdjava-conversion.mdresource-management.mdstring-parsing.mdutility-features.md
tile.json

resource-management.mddocs/

Resource Management

Automatic resource management with try-with-resources semantics through the Using utility. Provides safe resource handling that ensures proper cleanup even in the presence of exceptions.

Capabilities

Single Resource Management

Manage a single resource with automatic cleanup through the Releasable typeclass.

object Using {
  /**
   * Use a resource in a Try context with automatic cleanup
   * @param resource Resource to manage (call-by-name for lazy evaluation)
   * @param f Operation to perform on the resource
   * @param releasable Typeclass instance for resource cleanup
   * @return Try containing the result or any exception
   */
  def apply[R: Releasable, A](resource: => R)(f: R => A): Try[A]
  
  /**
   * Use a resource with automatic cleanup, throwing exceptions directly
   * @param resource Resource to manage
   * @param body Operation to perform on the resource
   * @param releasable Typeclass instance for resource cleanup
   * @return Result of the operation
   * @throws Exception if resource creation or operation fails
   */
  def resource[R, A](resource: R)(body: R => A)(implicit releasable: Releasable[R]): A
}

Usage Examples:

import scala.util.Using
import java.io.{FileInputStream, BufferedReader, FileReader}

// Using with Try (exception handling)
val content = Using(new FileInputStream("data.txt")) { fis =>
  val bytes = new Array[Byte](fis.available())
  fis.read(bytes)
  new String(bytes)
}

content match {
  case Success(data) => println(s"File content: $data")
  case Failure(exception) => println(s"Error reading file: $exception")
}

// Using with direct exceptions
try {
  val result = Using.resource(new BufferedReader(new FileReader("config.txt"))) { reader =>
    reader.lines().toArray.mkString("\n")
  }
  println(result)
} catch {
  case ex: Exception => println(s"Failed to read config: $ex")
}

// Combining with other operations
val processedData = Using(new FileInputStream("input.dat")) { inputStream =>
  val data = readBinaryData(inputStream)
  processData(data)
  transformData(data)
}.getOrElse(defaultData)

Multiple Resource Management

Manage multiple resources simultaneously with guaranteed cleanup of all resources.

/**
 * Manage two resources with automatic cleanup
 * @param resource1 First resource to manage
 * @param resource2 Second resource to manage (call-by-name)
 * @param body Operation to perform on both resources
 * @return Result of the operation
 * @throws Exception if any resource creation or operation fails
 */
def resources[R1: Releasable, R2: Releasable, A](
  resource1: R1, 
  resource2: => R2
)(body: (R1, R2) => A): A

/**
 * Manage three resources with automatic cleanup
 * @param resource1 First resource to manage
 * @param resource2 Second resource to manage (call-by-name)
 * @param resource3 Third resource to manage (call-by-name)
 * @param body Operation to perform on all resources
 * @return Result of the operation
 */
def resources[R1: Releasable, R2: Releasable, R3: Releasable, A](
  resource1: R1,
  resource2: => R2, 
  resource3: => R3
)(body: (R1, R2, R3) => A): A

/**
 * Manage four resources with automatic cleanup
 * @param resource1 First resource to manage
 * @param resource2 Second resource to manage (call-by-name)
 * @param resource3 Third resource to manage (call-by-name)
 * @param resource4 Fourth resource to manage (call-by-name)
 * @param body Operation to perform on all resources
 * @return Result of the operation
 */
def resources[R1: Releasable, R2: Releasable, R3: Releasable, R4: Releasable, A](
  resource1: R1,
  resource2: => R2,
  resource3: => R3, 
  resource4: => R4
)(body: (R1, R2, R3, R4) => A): A

Usage Examples:

import scala.util.Using
import java.io.{FileInputStream, FileOutputStream, BufferedReader, FileReader}

// Copy file with automatic cleanup of both streams
Using.resources(
  new FileInputStream("source.txt"),
  new FileOutputStream("destination.txt")
) { (input, output) =>
  val buffer = new Array[Byte](8192)
  var bytesRead = 0
  while ({ bytesRead = input.read(buffer); bytesRead != -1 }) {
    output.write(buffer, 0, bytesRead)
  }
}

// Process multiple files
Using.resources(
  new BufferedReader(new FileReader("config.properties")),
  new BufferedReader(new FileReader("data.csv")),
  new FileOutputStream("report.txt")
) { (configReader, dataReader, reportOutput) =>
  val config = loadProperties(configReader)
  val data = parseCSV(dataReader)
  val report = generateReport(config, data)
  reportOutput.write(report.getBytes)
}

// Database operations with multiple connections
Using.resources(
  createReadConnection(),
  createWriteConnection()
) { (readConn, writeConn) =>
  val data = fetchData(readConn)
  val processed = processData(data)
  saveResults(writeConn, processed)
}

Resource Manager

Advanced resource management with a manager that can handle an arbitrary number of resources.

final class Manager {
  /**
   * Register and return a resource for management
   * @param resource Resource to manage
   * @param releasable Typeclass instance for resource cleanup
   * @return The resource itself
   */
  def apply[R: Releasable](resource: R): R
  
  /**
   * Register a resource for management without returning it
   * @param resource Resource to manage
   * @param releasable Typeclass instance for resource cleanup
   */
  def acquire[R: Releasable](resource: R): Unit
}

object Manager {
  /**
   * Execute operation with resource manager
   * @param op Operation that uses the manager to acquire resources
   * @return Try containing the result or any exception
   */
  def apply[A](op: Manager => A): Try[A]
}

Usage Examples:

import scala.util.Using

// Managing multiple resources dynamically
val result = Using.Manager { manager =>
  val file1 = manager(new FileInputStream("file1.txt"))
  val file2 = manager(new FileInputStream("file2.txt"))
  val output = manager(new FileOutputStream("merged.txt"))
  
  // All resources are automatically managed
  mergeFiles(file1, file2, output)
}

// Conditional resource acquisition
Using.Manager { manager =>
  val primaryDB = manager(connectToPrimaryDatabase())
  
  val backupDB = if (primaryDB.isHealthy) {
    None
  } else {
    Some(manager(connectToBackupDatabase()))
  }
  
  performDatabaseOperations(primaryDB, backupDB)
}

// Resource acquisition in loops
Using.Manager { manager =>
  val outputFiles = (1 to 10).map { i =>
    manager(new FileOutputStream(s"output_$i.txt"))
  }
  
  // Process data and write to all files
  processDataToMultipleFiles(inputData, outputFiles)
}

Releasable Typeclass

The Releasable typeclass defines how resources should be cleaned up.

trait Releasable[-R] {
  /**
   * Release the resource, cleaning up any held resources
   * @param resource Resource to release
   */
  def release(resource: R): Unit
}

object Releasable {
  /**
   * Implicit instance for AutoCloseable resources
   */
  implicit object AutoCloseableIsReleasable extends Releasable[AutoCloseable] {
    def release(resource: AutoCloseable): Unit = resource.close()
  }
  
  /**
   * Implicit instance for scala.io.Source (Scala 2.11 only)
   */
  implicit object SourceReleasable extends Releasable[Source] {
    def release(resource: Source): Unit = resource.close()
  }
}

Custom Releasable Instances:

import scala.util.Using.Releasable

// Custom resource type
case class DatabaseConnection(url: String) {
  def close(): Unit = {
    // Close database connection
    println(s"Closing connection to $url")
  }
}

// Custom Releasable instance
implicit val dbReleasable: Releasable[DatabaseConnection] = new Releasable[DatabaseConnection] {
  def release(resource: DatabaseConnection): Unit = resource.close()
}

// Usage with custom resource
Using(DatabaseConnection("jdbc:mysql://localhost/db")) { conn =>
  // Perform database operations
  executeQuery(conn, "SELECT * FROM users")
}

// Thread pool management
case class ThreadPool(size: Int) {
  def shutdown(): Unit = {
    println(s"Shutting down thread pool of size $size")
  }
}

implicit val threadPoolReleasable: Releasable[ThreadPool] = 
  (resource: ThreadPool) => resource.shutdown()

Using(ThreadPool(10)) { pool =>
  // Execute tasks using thread pool
  executeTasks(pool, tasks)
}

Exception Handling Patterns

Graceful Degradation:

import scala.util.Using

def readConfigWithFallback(primaryPath: String, fallbackPath: String): Config = {
  Using(new FileInputStream(primaryPath)) { input =>
    parseConfig(input)
  }.recoverWith { case _ =>
    Using(new FileInputStream(fallbackPath)) { input =>
      parseConfig(input)
    }
  }.getOrElse(defaultConfig)
}

Resource Validation:

import scala.util.Using

def validateAndProcessFile(path: String): Either[String, ProcessedData] = {
  Using(new FileInputStream(path)) { input =>
    if (input.available() == 0) {
      Left("File is empty")
    } else {
      val data = readData(input)
      if (isValidData(data)) {
        Right(processData(data))
      } else {
        Left("Invalid data format")
      }
    }
  }.fold(
    exception => Left(s"Error reading file: ${exception.getMessage}"),
    identity
  )
}

Retry with Resources:

import scala.util.Using
import scala.util.control.NonFatal

def withRetry[A](maxAttempts: Int)(operation: => A): Option[A] = {
  (1 to maxAttempts).view.map { attempt =>
    try {
      Some(operation)
    } catch {
      case NonFatal(e) =>
        if (attempt == maxAttempts) {
          println(s"All $maxAttempts attempts failed")
          None
        } else {
          println(s"Attempt $attempt failed, retrying...")
          Thread.sleep(1000 * attempt) // Exponential backoff
          None
        }
    }
  }.find(_.isDefined).flatten
}

// Usage with resource management
withRetry(3) {
  Using(connectToDatabase()) { conn =>
    executeQuery(conn, "SELECT COUNT(*) FROM users")
  }.get
}

Advanced Patterns

Resource Pooling:

import scala.util.Using
import java.util.concurrent.BlockingQueue

class ResourcePool[R: Releasable](factory: () => R, maxSize: Int) {
  private val pool: BlockingQueue[R] = new java.util.concurrent.LinkedBlockingQueue[R](maxSize)
  
  def withResource[A](operation: R => A): A = {
    val resource = Option(pool.poll()).getOrElse(factory())
    try {
      val result = operation(resource)
      if (pool.remainingCapacity() > 0) {
        pool.offer(resource)
      } else {
        implicitly[Releasable[R]].release(resource)
      }
      result
    } catch {
      case exception =>
        implicitly[Releasable[R]].release(resource)
        throw exception
    }
  }
}

// Usage
val dbPool = new ResourcePool(() => createDatabaseConnection(), 10)

dbPool.withResource { conn =>
  executeQuery(conn, "SELECT * FROM products")
}

Nested Resource Management:

import scala.util.Using

def processNestedResources(): Unit = {
  Using(openArchive("data.zip")) { archive =>
    archive.entries.foreach { entry =>
      Using(archive.getInputStream(entry)) { entryStream =>
        Using(new BufferedReader(new InputStreamReader(entryStream))) { reader =>
          processFileContent(entry.getName, reader)
        }
      }
    }
  }
}

The Using utility provides a robust foundation for resource management in Scala applications, ensuring that resources are properly cleaned up regardless of whether operations succeed or fail.