├── .gitignore ├── project ├── build.properties └── plugins.sbt ├── .travis.yml ├── .scalafmt.conf ├── core └── src │ ├── test │ └── scala │ │ └── com │ │ └── iravid │ │ └── managedt │ │ └── ManagedTSpec.scala │ └── main │ └── scala │ └── com │ └── iravid │ └── managedt │ └── ManagedT.scala ├── tut └── src │ └── main │ └── tut │ └── README.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target/ -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.1.0 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15") 2 | addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.6.3") 3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3") 4 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: scala 3 | jdk: 4 | - oraclejdk8 5 | scala: 6 | - 2.11.12 7 | - 2.12.4 8 | cache: 9 | directories: 10 | - $HOME/.ivy2/cache 11 | - $HOME/.sbt/boot/ 12 | - $TRAVIS_BUILD_DIR/target 13 | - $TRAVIS_BUILD_DIR/project/target 14 | script: 15 | - sbt ++$TRAVIS_SCALA_VERSION ";test;tut" 16 | - find $HOME/.sbt -name "*.lock" -exec rm {} \; 17 | - find $HOME/.ivy2 -name "ivydata-*.properties" -exec rm {} \; 18 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | maxColumn = 100 2 | 3 | continuationIndent { 4 | callSite = 2 5 | defnSite = 2 6 | } 7 | 8 | spaces { 9 | inImportCurlyBraces = true 10 | } 11 | 12 | align { 13 | arrowEnumeratorGenerator = true 14 | openParenCallSite = false 15 | 16 | tokens = [ 17 | { code = "<-", owner = "Enumerator.Generator" } 18 | { code = "=", owner = "(Enumerator.Val|Term.Arg.Named)" } 19 | { code = "%", owner = "Term.ApplyInfix" } 20 | { code = "%%", owner = "Term.ApplyInfix" } 21 | { code = "=>", owner = "Case" } 22 | { code = "->", owner = "Term.Apply" } 23 | ] 24 | } 25 | 26 | binPack { 27 | parentConstructors = true 28 | } 29 | 30 | verticalMultilineAtDefinitionSite = false 31 | 32 | newlines { 33 | sometimesBeforeColonInMethodReturnType = true 34 | afterImplicitKWInVerticalMultiline = true 35 | } 36 | 37 | assumeStandardLibraryStripMargin = true 38 | 39 | rewrite.rules = [RedundantBraces, PreferCurlyFors, SortImports] -------------------------------------------------------------------------------- /core/src/test/scala/com/iravid/managedt/ManagedTSpec.scala: -------------------------------------------------------------------------------- 1 | package com.iravid.managedt 2 | 3 | import cats.kernel.Eq 4 | import monix.execution.Scheduler 5 | import monix.eval.Task 6 | import org.scalacheck.Arbitrary 7 | import org.scalatest.{ FunSuite, Matchers } 8 | import org.scalatest.concurrent.ScalaFutures 9 | import org.scalatest.prop.GeneratorDrivenPropertyChecks 10 | import org.typelevel.discipline.scalatest.Discipline 11 | import cats.kernel.laws.discipline.MonoidTests 12 | import cats.laws.discipline._ 13 | import cats.implicits._ 14 | import scala.concurrent.Await 15 | import scala.concurrent.duration._ 16 | 17 | class ManagedTSpec 18 | extends FunSuite with Discipline with GeneratorDrivenPropertyChecks with ScalaFutures 19 | with Matchers { 20 | import ManagedTSpec._ 21 | import monix.execution.Scheduler.Implicits.global 22 | 23 | test("ManagedT invokes cleanups in reverse order of acquiry") { 24 | forAll { resources: List[String] => 25 | var cleanups: List[String] = Nil 26 | 27 | val managed = 28 | resources.traverse(r => ManagedT(Task(r))(r => Task { cleanups = r :: cleanups })) 29 | 30 | val task = managed.unwrap 31 | 32 | val res2 = task.runAsync.futureValue 33 | 34 | res2 shouldBe resources 35 | cleanups shouldBe resources 36 | } 37 | } 38 | 39 | test("ManagedT invokes all cleanups, even if failures occur in cleanup") { 40 | forAll { resources: List[(String, Boolean)] => 41 | var cleanups: List[String] = Nil 42 | 43 | val managed = resources traverse { 44 | case (resource, shouldFail) => 45 | ManagedT(Task(resource)) { r => 46 | cleanups = r :: cleanups 47 | 48 | if (shouldFail) Task.raiseError(new Exception()) 49 | else Task.unit 50 | } 51 | } 52 | 53 | val task = managed.unwrap 54 | 55 | task.attempt.runAsync.futureValue 56 | 57 | cleanups shouldBe resources.unzip._1 58 | } 59 | } 60 | 61 | checkAll("Monad", MonadTests[ManagedT[Task, ?]].monad[String, String, String]) 62 | checkAll("Monoid", MonoidTests[ManagedT[Task, String]].monoid) 63 | } 64 | 65 | object ManagedTSpec { 66 | implicit def taskEq[A: Eq](implicit S: Scheduler): Eq[Task[A]] = Eq.instance { (a, b) => 67 | Await.result(a.runAsync, 30.seconds) === Await.result(b.runAsync, 30.seconds) 68 | } 69 | 70 | implicit def managedEq[R: Eq](implicit S: Scheduler): Eq[ManagedT[Task, R]] = Eq.by(_.unwrap) 71 | 72 | implicit def managedArbitrary[R: Arbitrary]: Arbitrary[ManagedT[Task, R]] = 73 | Arbitrary { 74 | Arbitrary.arbitrary[R].map { r => 75 | ManagedT(Task(r))(_ => Task.unit) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /core/src/main/scala/com/iravid/managedt/ManagedT.scala: -------------------------------------------------------------------------------- 1 | package com.iravid.managedt 2 | 3 | import cats.kernel.{ Monoid, Semigroup } 4 | import cats.{ Applicative, Monad, MonadError, StackSafeMonad } 5 | import cats.implicits._ 6 | 7 | abstract class ManagedT[F[_], R] { 8 | 9 | /** 10 | * Use the resource in this ManagedT in the provided function. Cleanup handlers will 11 | * be sequenced after the F[A] returned from use. 12 | */ 13 | def apply[A](use: R => F[A]): F[A] 14 | 15 | /** 16 | * Extract the resource in this ManagedT. The cleanup handlers will have already run 17 | * before actions sequenced after the returned F[A]. 18 | * 19 | * Using this doesn't make much sense if the cleanup handlers invalidate the resource, 20 | * so use with caution. 21 | */ 22 | def unwrap(implicit F: Applicative[F]): F[R] = apply(F.pure) 23 | } 24 | 25 | object ManagedT extends ManagedTLowPriority { 26 | 27 | /** 28 | * Construct a new ManagedT that acquires a resource using `acquire` and releases it 29 | * with `cleanup` after it is used. 30 | */ 31 | def apply[F[_], R](acquire: => F[R])(cleanup: R => F[Unit])( 32 | implicit FE: MonadError[F, Throwable]): ManagedT[F, R] = 33 | new ManagedT[F, R] { 34 | def apply[A](use: R => F[A]): F[A] = 35 | for { 36 | resource <- acquire 37 | resultOrError <- use(resource).attempt 38 | result <- resultOrError match { 39 | case Left(e) => cleanup(resource) *> e.raiseError[F, A] 40 | case Right(a) => cleanup(resource) as a 41 | } 42 | } yield result 43 | } 44 | 45 | /** 46 | * Lifts a value in `F` into `ManagedT[F, A]` *with no cleanup handler*. Use with 47 | * caution! 48 | */ 49 | def liftF[F[_], A](fa: => F[A])(implicit FE: MonadError[F, Throwable]): ManagedT[F, A] = 50 | ManagedT(fa)(_ => FE.unit) 51 | 52 | /** 53 | * Lifts a value into `ManagedT[F, A]` *with no cleanup handler*. Use with 54 | * caution! 55 | */ 56 | def pure[F[_]: MonadError[?[_], Throwable]] = new PartiallyAppliedBuilder[F] 57 | class PartiallyAppliedBuilder[F[_]](implicit FE: MonadError[F, Throwable]) { 58 | def apply[A](a: => A): ManagedT[F, A] = liftF(FE.pure(a)) 59 | } 60 | 61 | implicit def monad[F[_]]( 62 | implicit 63 | FE: MonadError[F, Throwable]): Monad[ManagedT[F, ?]] = 64 | new Monad[ManagedT[F, ?]] with StackSafeMonad[ManagedT[F, ?]] { 65 | def pure[A](x: A): ManagedT[F, A] = 66 | apply(x.pure[F])(_ => FE.unit) 67 | 68 | def flatMap[R1, R2](fa: ManagedT[F, R1])(f: R1 => ManagedT[F, R2]): ManagedT[F, R2] = 69 | new ManagedT[F, R2] { 70 | def apply[A](use: R2 => F[A]): F[A] = 71 | fa { r1 => 72 | f(r1) { r2 => 73 | use(r2) 74 | } 75 | } 76 | } 77 | 78 | override def tailRecM[R1, R2](r1: R1)(f: R1 => ManagedT[F, Either[R1, R2]]): ManagedT[F, R2] = 79 | new ManagedT[F, R2] { 80 | def apply[A](use: R2 => F[A]): F[A] = 81 | f(r1) { 82 | case Left(r1) => tailRecM(r1)(f)(use) 83 | case Right(r2) => use(r2) 84 | } 85 | } 86 | } 87 | 88 | implicit def monoid[F[_], A](implicit A: Monoid[A], 89 | FE: MonadError[F, Throwable]): Monoid[ManagedT[F, A]] = 90 | new Monoid[ManagedT[F, A]] { 91 | def combine(x: ManagedT[F, A], y: ManagedT[F, A]): ManagedT[F, A] = 92 | monad[F].map2(x, y)(A.combine) 93 | 94 | def empty: ManagedT[F, A] = monad[F].pure(A.empty) 95 | } 96 | } 97 | 98 | trait ManagedTLowPriority { 99 | implicit def semigroup[F[_], A](implicit FE: MonadError[F, Throwable], 100 | A: Semigroup[A]): Semigroup[ManagedT[F, A]] = 101 | new Semigroup[ManagedT[F, A]] { 102 | def combine(x: ManagedT[F, A], y: ManagedT[F, A]): ManagedT[F, A] = 103 | ManagedT.monad[F].map2(x, y)(A.combine) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tut/src/main/tut/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/iravid/managedt.svg?branch=master)](https://travis-ci.org/iravid/managedt) 2 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.iravid/managedt_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.iravid/managedt_2.12) 3 | 4 | # ManagedT: a monad for resource management 5 | 6 | `ManagedT[F[_], A]` is a monad for constructing computations that acquire and 7 | release resources. It is a translation of Gabriel Gonzalez' 8 | [managed](https://hackage.haskell.org/package/managed) library, with the 9 | difference that this library abstracts over the effect type. 10 | 11 | This library only contains typeclass instances for 12 | [cats](github.com/typelevel/cats). Scalaz already contains the `Codensity` data 13 | type which can serve the same functionality (with, albeit, some additional 14 | plumbing for cleanup handlers). 15 | 16 | ## IMPORTANT CAVEATS 17 | 18 | This library supports any `F[_]` that has `MonadError[F, Throwable]`. That 19 | includes `monix.eval.Task`, `cats.effect.IO`, `scala.concurrent.Future` (and 20 | others). 21 | 22 | However, it is very likely to be broken if: 23 | - you use cancellation with `Task` or `IO` - `MonadError` is not strong enough to express 24 | bracketing with cancellation, so that'll have to wait for `MonadBracket` in 25 | `cats-effect`; 26 | - you use it with a monad transformer such as `EitherT[IO, E, A]`, where there 27 | are several "layers" in which errors could be thrown; 28 | - you throw exceptions in the acquire or cleanup actions that are not sequenced 29 | into the `F[_]`; 30 | - you pass `Future` values that have already been executed in the acquire or 31 | cleanup functions. The library has constructors with by-name values in most 32 | places to assist with that. 33 | 34 | Other than that, it's all good ;-) The test suite exercises the common usecases; 35 | you are welcome to have a look. 36 | 37 | ## Usage 38 | 39 | ### SBT 40 | 41 | ```scala 42 | libraryDependencies += ״com.iravid" %% "managedt" % "0.1" 43 | ``` 44 | 45 | ### In your project 46 | 47 | First, create some resources: 48 | 49 | ```tut:book 50 | import com.iravid.managedt.ManagedT 51 | import monix.eval.Task 52 | 53 | val resource1 = ManagedT(Task { println("Acquiring 1"); 1 })(r => Task(println(s"Cleaning $r"))) 54 | val resource2 = ManagedT(Task { println("Acquiring 2"); 2 })(r => Task(println(s"Cleaning $r"))) 55 | ``` 56 | 57 | Compose them - `ManagedT[F, R]` is a monad in `R`, so you can use the usual forms of composition: 58 | ```tut:book 59 | import cats.implicits._ 60 | 61 | val zipped = (resource1, resource2).tupled 62 | val added = for { 63 | r1 <- resource1 64 | r2 <- resource2 65 | } yield r1 + r2 66 | 67 | val resources = (1 to 10).toList traverse { r => 68 | ManagedT(Task { println(s"Acquiring $r"); r})(r => Task(println(s"Cleaning $r"))) 69 | } 70 | ``` 71 | 72 | And, finally, use your acquired resource in a function of the form `R => F[A]`: 73 | ```tut:book 74 | val zippedUsage = zipped { case (r1, r2) => 75 | Task { 76 | println("Using zipped"); r1 + r2 77 | } 78 | } 79 | 80 | val addedUsage = added { r => 81 | Task { 82 | println("Using added"); r * r 83 | } 84 | } 85 | 86 | val resourcesUsage = resources { rs => 87 | Task { 88 | println("Using resources") 89 | rs.sum 90 | } 91 | } 92 | ``` 93 | 94 | As we're using `Task`, nothing has actually run yet; we've composed programs that use the resources safely. Let's see what happens when we run them: 95 | 96 | ```tut:silent 97 | import monix.execution.Scheduler 98 | implicit val scheduler = Scheduler.singleThread(name="tut") 99 | ``` 100 | 101 | ```tut:book 102 | import scala.concurrent.Await 103 | import scala.concurrent.duration._ 104 | 105 | Await.result(zippedUsage.runAsync, 5.seconds) 106 | ``` 107 | 108 | The program constructed by `ManagedT[F, A]` properly acquires and releases resources in the right order. This also works for the traversed list of resources: 109 | ```tut:book 110 | Await.result(resourcesUsage.runAsync, 5.seconds) 111 | ``` 112 | 113 | Should one of the acquires, clean-ups or uses fail, the clean-ups will still run properly: 114 | ```tut:book 115 | val acquireFailure = for { 116 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 1") }) 117 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 2")}) 118 | _ <- ManagedT(Task.raiseError(new Exception))((_: Unit) => Task { println("Cleaning 3") }) 119 | } yield () 120 | 121 | Await.result(acquireFailure(_ => Task.unit).attempt.runAsync, 5.seconds) 122 | 123 | val useFailure = for { 124 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 1") }) 125 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 2")}) 126 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 3") }) 127 | } yield () 128 | 129 | Await.result(useFailure(_ => Task.raiseError(new Exception())).attempt.runAsync, 5.seconds) 130 | 131 | val cleanupFailure = for { 132 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 1") }) 133 | _ <- ManagedT(Task.unit)(_ => Task.raiseError(new Exception())) 134 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 3") }) 135 | } yield () 136 | 137 | Await.result(cleanupFailure(_ => Task.unit).attempt.runAsync, 5.seconds) 138 | ``` 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | With cats-effect 1.0.0 out, this library is now redundant. The [`Resource`](https://typelevel.org/cats-effect/datatypes/resource.html) data type provides equivalent functionality. 4 | 5 | [![Build Status](https://travis-ci.org/iravid/managedt.svg?branch=master)](https://travis-ci.org/iravid/managedt) 6 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.iravid/managedt_2.12/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.iravid/managedt_2.12) 7 | 8 | # ManagedT: a monad for resource management 9 | 10 | `ManagedT[F[_], A]` is a monad for constructing computations that acquire and 11 | release resources. It is a translation of Gabriel Gonzalez' 12 | [managed](https://hackage.haskell.org/package/managed) library, with the 13 | difference that this library abstracts over the effect type. 14 | 15 | This library only contains typeclass instances for 16 | [cats](github.com/typelevel/cats). Scalaz already contains the `Codensity` data 17 | type which can serve the same functionality (with, albeit, some additional 18 | plumbing for cleanup handlers). 19 | 20 | ## IMPORTANT CAVEATS 21 | 22 | This library supports any `F[_]` that has `MonadError[F, Throwable]`. That 23 | includes `monix.eval.Task`, `cats.effect.IO`, `scala.concurrent.Future` (and 24 | others). 25 | 26 | However, it is very likely to be broken if: 27 | - you use cancellation with `Task` or `IO` - `MonadError` is not strong enough to express 28 | bracketing with cancellation, so that'll have to wait for `MonadBracket` in 29 | `cats-effect`; 30 | - you use it with a monad transformer such as `EitherT[IO, E, A]`, where there 31 | are several "layers" in which errors could be thrown; 32 | - you throw exceptions in the acquire or cleanup actions that are not sequenced 33 | into the `F[_]`; 34 | - you pass `Future` values that have already been executed in the acquire or 35 | cleanup functions. The library has constructors with by-name values in most 36 | places to assist with that. 37 | 38 | Other than that, it's all good ;-) The test suite exercises the common usecases; 39 | you are welcome to have a look. 40 | 41 | ## Usage 42 | 43 | ### SBT 44 | 45 | ```scala 46 | libraryDependencies += ״com.iravid" %% "managedt" % "0.1" 47 | ``` 48 | 49 | ### In your project 50 | 51 | First, create some resources: 52 | 53 | ```scala 54 | import com.iravid.managedt.ManagedT 55 | // import com.iravid.managedt.ManagedT 56 | 57 | import monix.eval.Task 58 | // import monix.eval.Task 59 | 60 | val resource1 = ManagedT(Task { println("Acquiring 1"); 1 })(r => Task(println(s"Cleaning $r"))) 61 | // resource1: com.iravid.managedt.ManagedT[monix.eval.Task,Int] = com.iravid.managedt.ManagedT$$anon$4@23497a65 62 | 63 | val resource2 = ManagedT(Task { println("Acquiring 2"); 2 })(r => Task(println(s"Cleaning $r"))) 64 | // resource2: com.iravid.managedt.ManagedT[monix.eval.Task,Int] = com.iravid.managedt.ManagedT$$anon$4@42716335 65 | ``` 66 | 67 | Compose them - `ManagedT[F, R]` is a monad in `R`, so you can use the usual forms of composition: 68 | ```scala 69 | import cats.implicits._ 70 | // import cats.implicits._ 71 | 72 | val zipped = (resource1, resource2).tupled 73 | // zipped: com.iravid.managedt.ManagedT[monix.eval.Task,(Int, Int)] = com.iravid.managedt.ManagedT$$anon$1$$anon$5@6d555633 74 | 75 | val added = for { 76 | r1 <- resource1 77 | r2 <- resource2 78 | } yield r1 + r2 79 | // added: com.iravid.managedt.ManagedT[monix.eval.Task,Int] = com.iravid.managedt.ManagedT$$anon$1$$anon$5@228720d0 80 | 81 | val resources = (1 to 10).toList traverse { r => 82 | ManagedT(Task { println(s"Acquiring $r"); r})(r => Task(println(s"Cleaning $r"))) 83 | } 84 | // resources: com.iravid.managedt.ManagedT[monix.eval.Task,List[Int]] = com.iravid.managedt.ManagedT$$anon$1$$anon$5@346d3ee3 85 | ``` 86 | 87 | And, finally, use your acquired resource in a function of the form `R => F[A]`: 88 | ```scala 89 | val zippedUsage = zipped { case (r1, r2) => 90 | Task { 91 | println("Using zipped"); r1 + r2 92 | } 93 | } 94 | // zippedUsage: monix.eval.Task[Int] = Task.FlatMap$1596984844 95 | 96 | val addedUsage = added { r => 97 | Task { 98 | println("Using added"); r * r 99 | } 100 | } 101 | // addedUsage: monix.eval.Task[Int] = Task.FlatMap$757626346 102 | 103 | val resourcesUsage = resources { rs => 104 | Task { 105 | println("Using resources") 106 | rs.sum 107 | } 108 | } 109 | // resourcesUsage: monix.eval.Task[Int] = Task.FlatMap$1046714810 110 | ``` 111 | 112 | As we're using `Task`, nothing has actually run yet; we've composed programs that use the resources safely. Let's see what happens when we run them: 113 | 114 | ```scala 115 | import monix.execution.Scheduler 116 | implicit val scheduler = Scheduler.singleThread(name="tut") 117 | ``` 118 | 119 | ```scala 120 | import scala.concurrent.Await 121 | // import scala.concurrent.Await 122 | 123 | import scala.concurrent.duration._ 124 | // import scala.concurrent.duration._ 125 | 126 | Await.result(zippedUsage.runAsync, 5.seconds) 127 | // Acquiring 1 128 | // Acquiring 2 129 | // Using zipped 130 | // Cleaning 2 131 | // Cleaning 1 132 | // res0: Int = 3 133 | ``` 134 | 135 | The program constructed by `ManagedT[F, A]` properly acquires and releases resources in the right order. This also works for the traversed list of resources: 136 | ```scala 137 | Await.result(resourcesUsage.runAsync, 5.seconds) 138 | // Acquiring 1 139 | // Acquiring 2 140 | // Acquiring 3 141 | // Acquiring 4 142 | // Acquiring 5 143 | // Acquiring 6 144 | // Acquiring 7 145 | // Acquiring 8 146 | // Acquiring 9 147 | // Acquiring 10 148 | // Using resources 149 | // Cleaning 10 150 | // Cleaning 9 151 | // Cleaning 8 152 | // Cleaning 7 153 | // Cleaning 6 154 | // Cleaning 5 155 | // Cleaning 4 156 | // Cleaning 3 157 | // Cleaning 2 158 | // Cleaning 1 159 | // res1: Int = 55 160 | ``` 161 | 162 | Should one of the acquires, clean-ups or uses fail, the clean-ups will still run properly: 163 | ```scala 164 | val acquireFailure = for { 165 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 1") }) 166 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 2")}) 167 | _ <- ManagedT(Task.raiseError(new Exception))((_: Unit) => Task { println("Cleaning 3") }) 168 | } yield () 169 | // acquireFailure: com.iravid.managedt.ManagedT[monix.eval.Task,Unit] = com.iravid.managedt.ManagedT$$anon$1$$anon$5@de37d32 170 | 171 | Await.result(acquireFailure(_ => Task.unit).attempt.runAsync, 5.seconds) 172 | // Cleaning 2 173 | // Cleaning 1 174 | // res2: Either[Throwable,Unit] = Left(java.lang.Exception) 175 | 176 | val useFailure = for { 177 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 1") }) 178 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 2")}) 179 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 3") }) 180 | } yield () 181 | // useFailure: com.iravid.managedt.ManagedT[monix.eval.Task,Unit] = com.iravid.managedt.ManagedT$$anon$1$$anon$5@6c1de753 182 | 183 | Await.result(useFailure(_ => Task.raiseError(new Exception())).attempt.runAsync, 5.seconds) 184 | // Cleaning 3 185 | // Cleaning 2 186 | // Cleaning 1 187 | // res3: Either[Throwable,Nothing] = Left(java.lang.Exception) 188 | 189 | val cleanupFailure = for { 190 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 1") }) 191 | _ <- ManagedT(Task.unit)(_ => Task.raiseError(new Exception())) 192 | _ <- ManagedT(Task.unit)(_ => Task { println("Cleaning 3") }) 193 | } yield () 194 | // cleanupFailure: com.iravid.managedt.ManagedT[monix.eval.Task,Unit] = com.iravid.managedt.ManagedT$$anon$1$$anon$5@1417b95d 195 | 196 | Await.result(cleanupFailure(_ => Task.unit).attempt.runAsync, 5.seconds) 197 | // Cleaning 3 198 | // Cleaning 1 199 | // res4: Either[Throwable,Unit] = Left(java.lang.Exception) 200 | ``` 201 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. --------------------------------------------------------------------------------