izumi.functional.lifecycle
Members list
Type members
Classlikes
Lifecycle
is a class that describes the effectful allocation of a resource and its finalizer.
This can be used to represent expensive resources.
Lifecycle
is a class that describes the effectful allocation of a resource and its finalizer.
This can be used to represent expensive resources.
Resources can be created using Lifecycle.make:
def open(file: File): Lifecycle[IO, BufferedReader] =
Lifecycle.make(
acquire = IO { new BufferedReader(new FileReader(file)) }
)(release = reader => IO { reader.close() })
Using inheritance from Lifecycle.Basic:
final class BufferedReaderResource(
file: File
) extends Lifecycle.Basic[IO, BufferedReader] {
def acquire: IO[BufferedReader] = IO { new BufferedReader(new FileReader(file)) }
def release(reader: BufferedReader): IO[BufferedReader] = IO { reader.close() }
}
Using constructor-based inheritance from Lifecycle.Make, Lifecycle.LiftF, etc:
final class BufferedReaderResource(
file: File
) extends Lifecycle.Make[IO, BufferedReader](
acquire = IO { new BufferedReader(new FileReader(file)) },
release = reader => IO { reader.close() },
)
Or by converting from an existing cats.effect.Resource or a zio.ZManaged:
- Use Lifecycle.fromCats, Lifecycle.SyntaxLifecycleCats#toCats to convert from and to a cats.effect.Resource
- And Lifecycle.fromZIO, Lifecycle.SyntaxLifecycleZIO#toZIO to convert from and to a zio.ZManaged
Usage is done via use:
open(file1).use {
reader1 =>
open(file2).use {
reader2 =>
readFiles(reader1, reader2)
}
}
Lifecycles can be combined into larger Lifecycles via Lifecycle#flatMap (and the associated for-comprehension syntax):
val res: Lifecycle[IO, (BufferedReader, BufferedReader)] = {
for {
reader1 <- open(file1)
reader2 <- open(file2)
} yield (reader1, reader2)
}
Nested resources are released in reverse order of acquisition. Outer resources are released even if an inner use or release fails.
Lifecycle
can be used without an effect-type with Lifecycle.Simple
it can also mimic Java's initialization-after-construction with Lifecycle.Mutable
Use Lifecycle's to specify lifecycles of objects injected into the object graph.
import distage.{Lifecycle, ModuleDef, Injector}
import cats.effect.IO
class DBConnection
class MessageQueueConnection
val dbResource = Lifecycle.make(IO { println("Connecting to DB!"); new DBConnection })(_ => IO(println("Disconnecting DB")))
val mqResource = Lifecycle.make(IO { println("Connecting to Message Queue!"); new MessageQueueConnection })(_ => IO(println("Disconnecting Message Queue")))
class MyApp(db: DBConnection, mq: MessageQueueConnection) {
val run = IO(println("Hello World!"))
}
val module = new ModuleDef {
make[DBConnection].fromResource(dbResource)
make[MessageQueueConnection].fromResource(mqResource)
make[MyApp]
}
Injector[IO]()
.produceGet[MyApp](module)
.use(_.run())
.unsafeRunSync()
Will produce the following output:
Connecting to DB!
Connecting to Message Queue!
Hello World!
Disconnecting Message Queue
Disconnecting DB
The lifecycle of the entire object graph is itself expressed with Lifecycle
,
you can control it by controlling the scope of .use
or by manually invoking
Lifecycle#acquire and Lifecycle#release.
== Inheritance helpers ==
The following helpers allow defining Lifecycle
sub-classes using expression-like syntax:
- Lifecycle.Of
- Lifecycle.OfInner
- Lifecycle.OfCats
- Lifecycle.OfZIO
- Lifecycle.LiftF
- Lifecycle.Make
- Lifecycle.Make_
- Lifecycle.MakePair
- Lifecycle.FromAutoCloseable
- Lifecycle.SelfOf
- Lifecycle.MutableOf
The main reason to employ them is to workaround a limitation in Scala 2's eta-expansion — when converting a method to a function value,
Scala always tries to fulfill implicit parameters eagerly instead of making them parameters of the function value,
this limitation makes it harder to inject implicits using distage
.
However, when using distage
's type-based syntax: make[A].fromResource[A.Resource[F]]
—
this limitation does not apply and implicits inject successfully.
So to workaround the limitation you can convert an expression based resource-constructor such as:
import distage.Lifecycle, cats.Monad
class A
object A {
def resource[F[_]](implicit F: Monad[F]): Lifecycle[F, A] = Lifecycle.pure(new A)
}
Into a class-based form:
import distage.Lifecycle, cats.Monad
class A
object A {
final class Resource[F[_]](implicit F: Monad[F])
extends Lifecycle.Of(
Lifecycle.pure(new A)
)
}
And inject successfully using make[A].fromResource[A.Resource[F]]
syntax of izumi.distage.model.definition.dsl.ModuleDefDSL.
The following helpers ease defining Lifecycle
sub-classes using traditional inheritance where acquire
/release
parts are defined as methods:
Attributes
- See also:
- Companion:
- object
- Graph
- Supertypes
- class Objecttrait Matchableclass Any
- Known subtypes
Attributes
- Companion:
- object
- Graph
- Supertypes
- class Objecttrait Matchableclass Any
Attributes
- Companion:
- class
- Graph
- Supertypes
- class Objecttrait Matchableclass Any
- Self type
- LifecycleAggregator.type