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 Fused to acquire the resource.The action in Fused to acquire the resource.- Note
- the - acquireaction is performed *uninterruptibly*, when- Fis an effect type that supports interruption/cancellation.
 
-   abstract  def extract[B >: A](resource: InnerResource): Either[F[B], B]Either an action in For a pure function used to extract theAfrom theInnerResourceEither an action in For a pure function used to extract theAfrom theInnerResourceThe effect in the Leftbranch will be performed *interruptibly*, it is not afforded the same kind of safety asacquireandreleaseactions whenFis an effect type that supports interruption/cancellation.When FisIdentity, it doesn't matter whether the output is aLeftorRightbranch.When consuming the output of extractyou can use_.fold(identity, F.pure)to convert theEithertoF[B]- See also
- Lifecycle.Basic - extractdoesn't have to be defined when inheriting from- Lifecycle.Basic
 
-   abstract  def release(resource: InnerResource): F[Unit]The action in Fused to release, close or deallocate the resource after it has been acquired and used through Lifecycle.SyntaxUse#use.The action in Fused to release, close or deallocate the resource after it has been acquired and used through Lifecycle.SyntaxUse#use.- Note
- the - releaseaction is performed *uninterruptibly*, when- Fis 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