trait Lifecycle[+F[_], +A] extends AnyRef
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, scoped zio.ZIO or a zio.managed.ZManaged:
- Use Lifecycle.fromCats, Lifecycle.SyntaxLifecycleCats#toCats to convert from and to a cats.effect.Resource
- Use Lifecycle.fromZIO, Lifecycle.SyntaxLifecycleZIO#toZIO to convert from and to a scoped zio.ZIO
- And Lifecycle.fromZManaged, Lifecycle.SyntaxLifecycleZManaged#toZManaged to convert from and to a zio.managed.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.OfZManaged
- Lifecycle.OfZLayer
- 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:
- Alphabetic
- By Inheritance
- Lifecycle
- AnyRef
- Any
- Hide All
- Show All
- Public
- Protected
Type Members
- abstract type InnerResource
Abstract Value Members
- abstract def acquire: F[InnerResource]
The action in
F
used to acquire the resource.The action in
F
used to acquire the resource.- Note
the
acquire
action is performed *uninterruptibly*, whenF
is an effect type that supports interruption/cancellation.
- abstract def extract[B >: A](resource: InnerResource): Either[F[B], B]
Either an action in
F
or a pure function used to extract theA
from theInnerResource
Either an action in
F
or a pure function used to extract theA
from theInnerResource
The effect in the
Left
branch will be performed *interruptibly*, it is not afforded the same kind of safety asacquire
andrelease
actions whenF
is an effect type that supports interruption/cancellation.When
F
isIdentity
, it doesn't matter whether the output is aLeft
orRight
branch.When consuming the output of
extract
you can use_.fold(identity, F.pure)
to convert theEither
toF[B]
- See also
Lifecycle.Basic
extract
doesn't have to be defined when inheriting fromLifecycle.Basic
- abstract def release(resource: InnerResource): F[Unit]
The action in
F
used to release, close or deallocate the resource after it has been acquired and used through izumi.distage.model.definition.Lifecycle.SyntaxUse#use.The action in
F
used to release, close or deallocate the resource after it has been acquired and used through izumi.distage.model.definition.Lifecycle.SyntaxUse#use.- Note
the
release
action is performed *uninterruptibly*, whenF
is an effect type that supports interruption/cancellation.
Concrete Value Members
- final def !=(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def ##: Int
- Definition Classes
- AnyRef → Any
- final def ==(arg0: Any): Boolean
- Definition Classes
- AnyRef → Any
- final def asInstanceOf[T0]: T0
- Definition Classes
- Any
- final def beforeAcquire[G[x] >: F[x]](f: => G[Unit])(implicit arg0: QuasiApplicative[G]): Lifecycle[G, A]
- final def beforeRelease[G[x] >: F[x]](f: (InnerResource) => G[Unit])(implicit arg0: QuasiApplicative[G]): Lifecycle[G, A]
Prepend release action to existing
- final def catchAll[G[x] >: F[x], B >: A](recover: (Throwable) => Lifecycle[G, B])(implicit arg0: QuasiIO[G]): Lifecycle[G, B]
- final def catchSome[G[x] >: F[x], B >: A](recover: PartialFunction[Throwable, Lifecycle[G, B]])(implicit arg0: QuasiIO[G]): Lifecycle[G, B]
- def clone(): AnyRef
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.CloneNotSupportedException]) @native()
- final def eq(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- def equals(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef → Any
- final def evalMap[G[x] >: F[x], B](f: (A) => G[B])(implicit arg0: QuasiPrimitives[G]): Lifecycle[G, B]
- final def evalTap[G[x] >: F[x]](f: (A) => G[Unit])(implicit arg0: QuasiPrimitives[G]): Lifecycle[G, A]
- def finalize(): Unit
- Attributes
- protected[lang]
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.Throwable])
- final def flatMap[G[x] >: F[x], B](f: (A) => Lifecycle[G, B])(implicit arg0: QuasiPrimitives[G]): Lifecycle[G, B]
- final def flatten[G[x] >: F[x], B](implicit arg0: QuasiPrimitives[G], ev: <:<[A, Lifecycle[G, B]]): Lifecycle[G, B]
- final def getClass(): Class[_ <: AnyRef]
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
- def hashCode(): Int
- Definition Classes
- AnyRef → Any
- Annotations
- @native()
- final def isInstanceOf[T0]: Boolean
- Definition Classes
- Any
- final def map[G[x] >: F[x], B](f: (A) => B)(implicit arg0: QuasiFunctor[G]): Lifecycle[G, B]
- final def ne(arg0: AnyRef): Boolean
- Definition Classes
- AnyRef
- final def notify(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
- final def notifyAll(): Unit
- Definition Classes
- AnyRef
- Annotations
- @native()
- final def redeem[G[x] >: F[x], B](onFailure: (Throwable) => Lifecycle[G, B], onSuccess: (A) => Lifecycle[G, B])(implicit arg0: QuasiIO[G]): Lifecycle[G, B]
- final def synchronized[T0](arg0: => T0): T0
- Definition Classes
- AnyRef
- def toString(): String
- Definition Classes
- AnyRef → Any
- final def void[G[x] >: F[x]](implicit arg0: QuasiFunctor[G]): Lifecycle[G, Unit]
- final def wait(): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long, arg1: Int): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException])
- final def wait(arg0: Long): Unit
- Definition Classes
- AnyRef
- Annotations
- @throws(classOf[java.lang.InterruptedException]) @native()
- final def widen[B >: A]: Lifecycle[F, B]
- Annotations
- @inline()
- final def widenF[G[x] >: F[x]]: Lifecycle[G, A]
- Annotations
- @inline()
- final def wrapAcquire[G[x] >: F[x]](f: (=> G[InnerResource]) => G[InnerResource]): Lifecycle[G, A]
Wrap acquire action of this resource in another effect, e.g.
Wrap acquire action of this resource in another effect, e.g. for logging purposes
- final def wrapRelease[G[x] >: F[x]](f: ((InnerResource) => G[Unit], InnerResource) => G[Unit]): Lifecycle[G, A]
Wrap release action of this resource in another effect, e.g.
Wrap release action of this resource in another effect, e.g. for logging purposes