Alexey Shuksto
Self-documented.
val i = 1
// Stack: push -> pop
val o = new Object
// Heap: init -> use -> gc (?)
val t: new Thread(...); t.start()
// Heap: init -> start -> run -> join ...
val session = DbSession.connect(..)
// Heap: connect -> use <-> reconnect -> close ...
val tx = session.beginTransaction(..)
// Heap: begin -> snapshot <-> rollback|commit|error
interface AutoCloseable {
void close() throws Exception;
}
var file = File.createTempFile("rcl-", ".tmp");
try(
var fw = new FileWriter(file);
var bw = new BufferedWriter(fw);
) {
bw.write("Hello World!");
}
trait Logger extends AutoCloseable:
def name: String
def printLine(s: String): Unit = ???
def close(): Unit = ???
end Logger
trait Metrics:
def apply[A](metric: String)(f: => A): A
def get: SortedMap[String, Metrics.Metric]
def clear(): Unit
end Metrics
object Using:
trait Releasable[-A]:
def release(a: A): Unit
given [A <: AutoCloseable]: Releasable[A]
with
def release(a: A): Unit = a.close()
def apply[A: Releasable, B](a: A)(
f: A => B
): B = ???
object Using:
def apply[A: Releasable, B](a: A)(
f: A => B
): B =
object Using:
def apply[A: Releasable, B](a: A)(
f: A => B
): B =
var toThrow: Throwable = null
try f(a)
catch case NonFatal(e) =>
toThrow = e
null.asInstanceOf[B]
finally
try summon[Releasable[A]].release(a)
catch case NonFatal(e) =>
if toThrow ne null then toThrow.addSuppressed(e)
else toThrow = e
if toThrow ne null then throw toThrow
end try
end apply
def sum(x: Int, y: Int): Int =
Using(Logger("log")): log =>
log.printLine(s"will sum x = $x and y = $y")
x + y
scala> sum(2, 2)
-- Printer 'log' is acquired.
log: will sum x = 2 and y = 2
-- Printer 'log' is released
val res6: Int = 4
def sumN(n: Int): Seq[Int] =
Using(Logger("log")): log =>
Using(Metrics()): meter =>
log.printLine(
s"will sum $n ranges [i..$n] where i in [0..$n]"
)
(0 to n).map: i =>
meter("vector"):
log.printLine(s"will sum range [$i, $n]")
(i to n).foldLeft(0): (acc, i) =>
meter("sum")(acc + i)
object Using:
def apply[A1: Releasable, A2: Releasable, B](
a1: A1, a2: => A2
)(f: (A1, A2) => B): B =
apply(a1): a1 =>
apply(a2): a2 =>
f(a1, a2)
def sumN(n: Int): Seq[Int] =
Using(Logger("log"), Metrics()): (log, meter) =>
log.printLine(
s"will sum $n ranges [i..$n] where i in [0..$n]"
)
val result = (0 to n).map: i =>
meter("vector"):
log.printLine(s"will sum range [$i, $n]")
(i to n).foldLeft(0): (acc, i) =>
meter("sum")(acc + i)
log.printLine(s"metrics:")
meter.get.foreach: (name, metric) =>
import metric.*
log.printLine(
s" $name -> $mean ± $variance ($count samples)"
)
result
scala> sumN(3)
-- Printer 'log' is acquired.
log: will sum 3 ranges [i..3] where i in [0..3]
log: will sum range [0, 3]
log: will sum range [1, 3]
log: will sum range [2, 3]
log: will sum range [3, 3]
log: metrics:
log: sum -> 193.2 ± 91.90516851624832 (10 samples)
log: vector -> 65496.75 ± 70337.1617652255 (4 samples)
-- Printer 'log' is released
val res6: Seq[Int] = Vector(6, 6, 5, 3)
object Metrics:
def apply(): Metrics = ???
def logging(): Metrics =
Using(Logger("metrics")): log =>
Logging(Metrics(), log)
private[Metrics] final class Logging(
underlying: Metrics,
log: Logger,
) extends Metrics:
def apply[A](metric: String)(f: => A): A = underlying(metric)(f)
def get: SortedMap[String, Metric] = underlying.get
def clear(): Unit =
log.printLine("collected metrics:")
get.foreach: (name, metric) =>
import metric.*
log.printLine(
f" $name%s -> $mean%.2f ± $variance%.2f ($count%d samples)"
)
underlying.clear()
end Logging
def sumN_logging_uar(n: Int): Seq[Int] =
Using(
Logger("log"),
Metrics.logging()
): (log, meter) =>
log.printLine(...)
(0 to n).map: i =>
meter("vector"):
log.printLine(...)
(i to n).foldLeft(0): (acc, i) =>
meter("sum")(acc + i)
scala> sumN_logging_uar(3)
-- Printer 'log' is acquired.
-- Printer 'metrics' is acquired.
-- Printer 'metrics' is released
log: will sum 3 ranges [i..3] where i in [0..3]
log: will sum range [0, 3]
log: will sum range [1, 3]
log: will sum range [2, 3]
log: will sum range [3, 3]
metrics: !!! WARN !!! Use of printer 'metrics' after release.
metrics: collected metrics:
metrics: !!! WARN !!! Use of printer 'metrics' after release.
metrics: sum -> 209.70 ± 117.62 (10 samples)
metrics: !!! WARN !!! Use of printer 'metrics' after release.
metrics: vector -> 131325.25 ± 181341.81 (4 samples)
-- Printer 'log' is released
val res7: Seq[Int] = Vector(6, 6, 5, 3)
object Metrics:
def logging(log: Logger): Metrics =
Logging(Metrics(), log)
def sumN_logging_using(n: Int): Seq[Int] =
Using(Logger("log")): log =>
Using(Metrics.logging(log)): meter =>
log.printLine(...)
(0 to n).map: i =>
meter("vector"):
log.printLine(...)
(i to n).foldLeft(0): (acc, i) =>
meter("sum")(acc + i)
scala> sumN_logging_using(3)
-- Printer 'log' is acquired.
log: will sum 3 ranges [i..3] where i in [0..3]
log: will sum range [0, 3]
log: will sum range [1, 3]
log: will sum range [2, 3]
log: will sum range [3, 3]
log: collected metrics:
log: sum -> 395.30 ± 233.92 (10 samples)
log: vector -> 82291.75 ± 42930.94 (4 samples)
-- Printer 'log' is released
val res8: Seq[Int] = Vector(6, 6, 5, 3)
object Using:
object Manager:
def apply[A](f: Manager => A): A =
Using(new Manager())(f(_))
final class Manager private () extends AutoCloseable:
private var closed = false
private var handles = List.empty[() => Unit]
def apply[A: Releasable](a: A): a.type =
if !closed then
val r = () => summon[Releasable[A]].release(a)
handles = r :: handles
else throw new IllegalStateException(
"Manager has already been closed"
)
a
def close(): Unit =
...
end Manager
object Using:
final class Manager private () extends AutoCloseable:
def close(): Unit =
closed = true
val toRelease = handles
var toThrow: Throwable = null
handles = null
toRelease.foreach: release =>
try release()
catch
case NonFatal(e) =>
if toThrow ne null then e.addSuppressed(toThrow)
toThrow = e
if toThrow ne null then throw toThrow
end close
end Manager
def sumN_manager(n: Int): Seq[Int] =
Using.Manager: use =>
val log = use(Logger("log"))
val meter = use(Metrics.logging(log))
...
scala> sumN_manager(3)
-- Printer 'log' is acquired.
log: will sum 3 ranges [i..3] where i in [0..3]
log: will sum range [0, 3]
log: will sum range [1, 3]
log: will sum range [2, 3]
log: will sum range [3, 3]
log: collected metrics:
log: sum -> 4243.90 ± 12186.67 (10 samples)
log: vector -> 538371.75 ± 870094.07 (4 samples)
-- Printer 'log' is released
val res9: Seq[Int] = Vector(6, 6, 5, 3)
// Functions
def sum_implicit(x: Int, y: Int)(implicit log: Logger) = ...
def repeat(i: Int)(f: Logger => A): Vector[A] =
val log = new Logger("repeat")
(0 until i).map(_ => f(log))
// repeat(3)(_ => sum_implicit(2, 2))
repeat(3)(_ => sum(2, 2))
repeat(3) { implicit log =>
sum_implicit(2, 2)
}
// Context Functions
def sum_implicit(x: Int, y: Int)(implicit log: Logger) = ...
def repeat(i: Int)(f: Logger ?=> A): Vector[A] =
val log = new Logger("repeat")
(0 until i).map(_ => f(using log))
repeat(3)(sum_implicit(2, 2))
repeat(3) { log ?=>
sum_implicit(2, 2)
}
package managed
final class Manager private[managed] ():
private var closed = false
private var handles = List.empty[() => Unit]
private[managed] def handle(f: () => Unit): Unit =
...
private[managed] def close(): Unit =
...
def manage[A](f: Manager ?=> A): A =
val manager = Manager()
try f(using manager)
catch ...
finally ...
def defer(f: => Unit)(using M: Manager): Unit =
M.handle(() => f)
def use[A](a: A)(using M: Manager, R: Using.Releasable[A]): A =
defer(R.release(a))
a
def sumN(n: Int): Seq[Int] = manage:
val log = use(Logger("log"))
val meter = use(Metrics.logging(log))
log.printLine(...)
defer(log.printLine("'sumN' completed"))
(0 to n).map: i =>
meter("vector"):
log.printLine(...)
(i to n).foldLeft(0): (acc, i) =>
meter("sum")(acc + i)
scala> sumN(3)
-- Printer 'log' is acquired.
log: will sum 3 ranges [i..3] where i in [0..3]
...
log: will sum range [3, 3]
log: 'sumN' completed
log: collected metrics:
log: sum -> 339.30 ± 186.86 (10 samples)
log: vector -> 71306.50 ± 14343.61 (4 samples)
-- Printer 'log' is released
val res1: Seq[Int] = Vector(6, 6, 5, 3)
// [A, B] =>> (() => A)(A => B)(A => ())
def bracket[A, B](acquire: => A)(
use: A => B
)(release: A => Unit): B =
val a = acquire
try use(a)
catch ...
finally
try release(a)
catch ...
def sumN(n: Int): Seq[Int] =
bracket:
val log = Logger("log")
val meter = Metrics.logging(log)
(log, meter)
.apply: (log, meter) =>
log.printLine(...)
(0 to n).map: i =>
meter("vector"):
log.printLine(...)
(i to n).foldLeft(0): (acc, i) =>
meter("sum")(acc + i)
.apply: (log, meter) =>
meter.clear()
log.close()
Resource acquisition is initialization:
trait Resource[A]:
def allocate: (A, () => Unit)
def use[B](f: A => B): B =
val (a, release) = allocate
try f(a)
catch ...
finally
try release()
catch ...
object Resource:
def apply[A](acquire: => A)(release: A => Unit): Resource[A] =
new Resource[A]:
def allocate: (A, () => Unit) =
val a = acquire
(a, () => release(a))
object Logger:
def resource(name: String): Resource[Logger] =
Resource(Logger(name))(_.close())
trait Resource[A]:
def map[B](f: A => B): Resource[B] =
Resource.Map(this, f)
def flatMap[B](f: A => Resource[B]): Resource[B] =
Resource.Bind(this, f)
object Resource:
final class Map[A, B](
underlying: Resource[A],
f: A => B
) extends Resource[B]:
def allocate: (B, () => Unit) =
val (a, release) = underlying.allocate
(f(a), release)
final class Bind[A, B](
underlying: Resource[A],
f: A => Resource[B]
) extends Resource[B]:
def allocate: (B, () => Unit) =
...
object Resource:
final class Bind[A, B](underlying: Resource[A], f: A => Resource[B])
extends Resource[B]:
def allocate: (B, () => Unit) =
val (a, releaseA) = underlying.allocate
try
val (b, releaseB) = f(a).allocate
val releaseBoth = () =>
var toThrow: Throwable = null
try releaseB()
catch case NonFatal(e) => toThrow = e
finally
try releaseA()
catch
case NonFatal(e) =>
if toThrow ne null then e.addSuppressed(toThrow)
toThrow = e
end try
if toThrow ne null then throw toThrow
(b, releaseBoth)
catch
case NonFatal(e) =>
try releaseA()
catch
case NonFatal(e2) =>
e.addSuppressed(e2)
throw e
object Metrics:
def resource(): Resource[Metrics] =
Resource(Metrics())(_.clear())
def lazyLogging(): Resource[Metrics] =
resource().flatMap: origin =>
val log = Logger.resource("metrics")
Resource(LazyLogging(origin, log))(_.clear())
final class LazyLogging(
underlying: Metrics,
log: Resource[Logger],
) extends Metrics:
def apply[A](metric: String)(f: => A): A = underlying(metric)(f)
def get: SortedMap[String, Metric] = underlying.get
def clear(): Unit = log.use : log =>
log.printLine("collected metrics:")
get.foreach: (name, metric) =>
import metric.*
log.printLine(
f" $name%s -> $mean%.2f ± $variance%.2f ($count%d samples)"
)
def sum(x: Int, y: Int): Int =
Logger.resource("log").use: log =>
log.printLine(s"will sum x = $x and y = $y")
x + y
scala> sum(2, 2)
-- Printer 'log' is acquired.
log: will sum x = 2 and y = 2
-- Printer 'log' is released
val res6: Int = 4
def sumN(n: Int): Seq[Int] =
val resources = for
log <- Logger.resource("log")
metrics <- Metrics.lazyLogging()
yield (log, metrics)
resources.use: (log, meter) =>
log.printLine(...)
(0 to n).map: i =>
meter("vector"):
log.printLine(...)
(i to n).foldLeft(0): (acc, i) =>
meter("sum")(acc + i)
-- Printer 'log' is acquired.
log: will sum 3 ranges [i..3] where i in [0..3]
log: will sum range [0, 3]
log: will sum range [1, 3]
log: will sum range [2, 3]
log: will sum range [3, 3]
-- Printer 'metrics' is acquired.
metrics: collected metrics:
metrics: sum -> 420.50 ± 199.81 (10 samples)
metrics: vector -> 75685.25 ± 40932.68 (4 samples)
-- Printer 'metrics' is released
-- Printer 'log' is released
val res4: Seq[Int] = Vector(6, 6, 5, 3)
def acquire[A](r: Resource[A])(using M: Manager): A =
val (a, release) = r.allocate
defer(release())
a
def sumN_acquire(n: Int): Seq[Int] = manage:
val log = acquire(Logger.resource("log"))
val meter = acquire(Metrics.lazyLogging())
log.printLine(...)
(0 to n).map: i =>
meter("vector"):
log.printLine(...)
(i to n).foldLeft(0): (acc, i) =>
meter("sum")(acc + i)
end sumN_acquire
// 2.* Tuples:
// Tuple1[+T1](_1: T1) ... Tuple22[+T1, .., +T22](...)
//
// 3.* Tuples
sealed trait Tuple
case object EmptyTuple extends Tuple
sealed trait NonEmptyTuple extends Tuple:
def head: Head[this.type]
def tail: Tail[this.type]
sealed abstract class *:[+H, +T <: Tuple]
extends NonEmptyTuple
// Match Types
type Head[X <: NonEmptyTuple] = X match
case h *: _ => h
type Tail[X <: NonEmptyTuple] = X match
case _ *: t => t
type Leaf[X] = X match
case String => Char
case Iterable[t] => Leaf[t]
case _ => X
def leaf[X](x: X): Leaf[X] = x match
case s: String => s.head
case i: Iterable[t] => leaf(i.head)
case _ => x
type ResourceParams[X <: NonEmptyTuple] <: NonEmptyTuple =
X match
case Resource[a] *: EmptyTuple => a *: EmptyTuple
case Resource[a] *: tail => a *: ResourceParams[tail]
def managing[X <: NonEmptyTuple, A](rs: X)(
f: ResourceParams[X] => Manager ?=> A
)(using ev: Tuple.Union[X] <:< Resource[?]): A = manage:
def loop(rest: NonEmptyTuple, acc: Tuple): NonEmptyTuple =
(rest: @unchecked) match
case (r: Resource[a]) *: EmptyTuple =>
acc :* acquire(r)
case (r: Resource[a]) *: (tail: NonEmptyTuple) =>
loop(tail, acc :* acquire(r))
f(loop(rs, EmptyTuple).asInstanceOf[ResourceParams[X]])
def sumN_manage(n: Int): Seq[Int] =
managing(
Logger.resource("log"),
Metrics.lazyLogging()
): (log, meter) =>
log.printLine(...)
(0 to n).map: i =>
meter("vector"):
log.printLine(...)
(i to n).foldLeft(0): (acc, i) =>
meter("sum")(acc + i)
List<Integer> sumN_manage(n: Int) {
try(
var log = Logger.resource("log"),
var meter = Metrics.lazyLogging()
) { ... }
}
def sumN(n: Int): Seq[Int] = manage:
val log = acquire(Logger.resource("log"))
val (seqMetrics, sumMetrics) = Metrics
.loggingResource()
.use: metrics =>
(metrics[Int]("vector"), metrics[Int]("sum"))
log.printLine(...)
(0 to n).map: i =>
seqMetrics:
log.printLine(...)
(i to n).foldLeft(0): (acc, i) =>
sumMetrics:
acc + i
end sumN
scala> sumN(3)
-- Printer 'log' is acquired.
-- Printer 'metrics' is acquired.
metrics: collected metrics:
-- Printer 'metrics' is released
log: will sum 3 ranges [i..3] where i in [0..3]
log: will sum range [0, 3]
log: will sum range [1, 3]
log: will sum range [2, 3]
log: will sum range [3, 3]
-- Printer 'log' is released
val res2: Seq[Int] = Vector(6, 6, 5, 3)
CAPabilities for RESources and Effects
object Metrics:
def logging(log: Logger): Metrics =
...
val log: Logger = Logger("log")
val metrics: Metrics = Metrics.logging(log)
// using option "-experimental"
import scala.language.experimental.captureChecking
object Metrics:
def logging(log: Logger^): Metrics^{log} =
...
// won't compile
def createMetrics(): Metrics =
val log: Logger^ = Logger("log")
val metrics: Metrics^{log} = Metrics.logging(log)
metrics
// pure, captures nothing
def log1[A, B](l: Logger)(f: A -> B): A -> B = ...
// impure, captures ^{l} (logger)
def log2[A, B](l: Logger^)(f: A ->{l} B): A ->{l} B = ...
// impure, captures ^{cap} (anything)
def log3[A, B](l: Logger^)(f: A => B): A => B = ...
val l1: Logger = Logger("test")
log1(l1)((i: Int) => i.toString)
val l2: Logger^ = Logger("test")
log2(l1)((i: Int) => i.toString)
val meter: Metrics^ = Metrics.logging(l2)
log3(l1)((i: Int) => meter(i.toString))
trait Resource[A]:
def allocate: (A, () -> Unit)
def use[B](f: A^ => B): B =
...
object Resource:
def apply[A](
acquire: -> A
)(
release: A -> Unit
): Resource[A] =
...
def sum_error(x: Int, y: Int): Int =
val print = logger("log").use: log =>
(s: String) => log.printLine(s)
print(s"will sum x = $x and y = $y")
x + y
$> scala-cli run project.scala captured.scala
Compiling project (Scala 3.4.2, JVM (21))
[error] ./captured.scala:105:17
[error] local reference log leaks into
[error] outer capture set of type parameter B of method use
[error] val print = logger("log").use: log =>
[error] ^^^^^^^^^^^^^^^^^
@capability
final class Manager private[managed] ():
self =>
private var handles: List[() ->{self} Unit]^{self} = Nil
private[captured] def handle(f: () ->{self} Unit): Unit =
...
private[captured] def close(): Unit =
...
def defer(using M: Manager)(f: ->{M} Unit): Unit =
M.handle(() => f)
def sumN(n: Int): Seq[Int] = manage:
val log = acquire(logger("log"))
log.printLine(...)
(0 to n).map: i =>
log.printLine(...)
(i to n).foldLeft(0): (acc, i) =>
acc + i
def sumN_delay(n: Int): Seq[Int] = manage:
val print =
val log = acquire(logger("log"))
(s: String) => log.printLine(s)
print(...)
(0 to n).map: i =>
print(...)
(i to n).foldLeft(0): (acc, i) =>
acc + i
def sumN_error(n: Int): Seq[Int] =
val print = manage:
val log = acquire(logger("log"))
(s: String) => log.printLine(s)
print(...)
(0 to n).map: i =>
print(...)
(i to n).foldLeft(0): (acc, i) =>
acc + i
$> scala-cli run project.scala captured.scala
Compiling project (Scala 3.4.2, JVM (21))
[error] ./captured.scala:132:17
[error] local reference contextual$3 leaks into
[error] outer capture set of type parameter A of method manage
[error] val print = manage:
[error] ^^^^^^
scala.util.Using
twitter.util.Managed
cats.effect.Resource
zio.Scope
By Alexey Shuksto
Handling of object and resource lifecycles in Scala