-
Notifications
You must be signed in to change notification settings - Fork 516
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Bracket type class #113
Changes from 26 commits
a318a4c
1b8f273
1f12aba
75d8a43
a479184
8ffffcd
e507a3a
f2ee74f
3bb2106
43f5488
5e7119e
5fa474e
e4cc1fa
4c2bea6
595b1b0
cb53957
db757aa
87e2cec
96538fd
859d50f
45edf70
e3e730e
e88bef3
6c41b50
0bd2149
d91060e
54e176c
590e6bd
7a38584
1f2b8b2
b994d83
6838b7e
596c17a
e26f667
05fd2e6
eadba2f
d3b7879
a82d3fc
c70cdec
7a2b91b
b6aebec
e6c524c
00d6f5b
09f83c5
646ec4c
05032dd
cd9c318
796c519
aab8173
8a4b74f
f536d79
84edccd
28a05b4
008f29f
596f0cd
5bbbea7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/* | ||
* Copyright (c) 2017-2018 The Typelevel Cats-effect Project Developers | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package cats | ||
package effect | ||
|
||
trait Bracket[F[_], E] extends MonadError[F, E] { | ||
def bracket[A, B](acquire: F[A])(use: A => F[B]) | ||
(release: (A, BracketResult[E]) => F[Unit]): F[B] | ||
} | ||
|
||
sealed abstract class BracketResult[+E] | ||
|
||
object BracketResult { | ||
case object Completed extends BracketResult[Nothing] | ||
case class Error[+E](e: E) extends BracketResult[E] | ||
case class Canceled[+E](e: Option[E]) extends BracketResult[E] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have asked this before, but I don’t see where we need to pass the E here. I don’t see us using this feature in this code. Has this changed at all with the introduction of Concurrent? If we do need the Option E can we add a comment as to why here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for responding so late @johnynek, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. N.B. our The cool thing about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see y’all really want this, but I would make the call of either always requiring it or never. For instance can the cancelation type be a type member of MonadBracket? In a usual case it may be E but for some instances it can be E. My concern is that it should not be dynamic, but statically known based on the Bracket. It feels like making this look like it depends on the data is the wrong way to go. I think we could do this if we move this whole type inside trait MonadBracket. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Side note: if you don’t support cancelation you can make the Canceled wrap Nothing, which is proof it never happens. |
||
|
||
def complete[E]: BracketResult[E] = Completed | ||
def error[E](e: E): BracketResult[E] = Error[E](e) | ||
def cancelled[E](e: Option[E] = None): BracketResult[E] = Canceled(e) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cats usually doesn’t use default parameters due to how to interacts with converting to lambdas or something I thought. Should we have two methods, cancelled and maybe cancelledWith? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there plans to pick a consistent spelling for canceled? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. English hasn't picked a consistent spelling, but we should. Good catch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is my mistake, thanks! |
||
|
||
def attempt[E, A](value: Either[E, A]): BracketResult[E] = | ||
value match { | ||
case Left(e) => BracketResult.error(e) | ||
case Right(_) => BracketResult.complete | ||
} | ||
} | ||
|
||
object Bracket { | ||
def apply[F[_], E](implicit ev: Bracket[F, E]): Bracket[F, E] = ev | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. want to implement |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,8 +22,9 @@ import cats.effect.internals._ | |
import cats.effect.internals.Callback.Extensions | ||
import cats.effect.internals.TrampolineEC.immediate | ||
import cats.effect.internals.IOPlatform.fusionMaxStackDepth | ||
|
||
import scala.annotation.unchecked.uncheckedVariance | ||
import scala.concurrent.{ExecutionContext, Future, Promise} | ||
import scala.concurrent.{CancellationException, ExecutionContext, Future, Promise} | ||
import scala.concurrent.duration._ | ||
import scala.util.{Failure, Left, Right, Success} | ||
|
||
|
@@ -383,6 +384,19 @@ sealed abstract class IO[+A] extends internals.IOBinaryCompat[A] { | |
final def to[F[_]](implicit F: LiftIO[F]): F[A @uncheckedVariance] = | ||
F.liftIO(this) | ||
|
||
private final val cancelException = new CancellationException("cancel in bracket") | ||
|
||
def bracket[B](use: A => IO[B])(release: (A, BracketResult[Throwable]) => IO[Unit]): IO[B] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note due to the cancellable IO changes, this needs a different implementation. Fortunately I've already thought about it, by introducing See implementation: https://github.com/monix/monix/blob/master/monix-eval/shared/src/main/scala/monix/eval/internal/TaskBracket.scala#L35 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're the best! |
||
for { | ||
a <- this | ||
etb <- use(a).onCancelRaiseError(cancelException).attempt | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For See for example handleErrorWith that is effectively a What this means is that you can do the same thing that I did in Monix's TaskBracket. Also I would move this whole implementation into an I love transparent, elegant FP implementations, but it's far better to kick some ass in terms of performance 😀 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're absolutely right 😄 |
||
_ <- release(a, etb match { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we not use attempt on etb? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BracketResult.attempt I mean. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But why do we unconditionally send error on an exception rather than sending There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, will need to fix. |
||
case Left(e) => BracketResult.error[Throwable](e) | ||
case Right(_) => BracketResult.complete | ||
}) | ||
b <- IO.fromEither(etb) | ||
} yield b | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in library code, I prefer to write things the most efficient way, Wonder about all the use of fold and allocation of closures here. What about: for {
a <- this
etb <- use(a).attempt
_ <- release(a, etb)
b <- IO.fromEither(etb)
} yield b Also note, def fromEither[A](e: Either[Throwable, A]): IO[A] =
e match {
case Right(a) => pure(a)
case Left(err) => raiseError(err)
} and avoid the allocations of the closures. |
||
|
||
override def toString = this match { | ||
case Pure(a) => s"IO($a)" | ||
case RaiseError(e) => s"IO(throw $e)" | ||
|
@@ -486,6 +500,11 @@ private[effect] abstract class IOInstances extends IOLowPriorityInstances { | |
case Left(a) => tailRecM(a)(f) | ||
case Right(b) => pure(b) | ||
} | ||
|
||
override def bracket[A, B](acquire: IO[A]) | ||
(use: A => IO[B]) | ||
(release: (A, BracketResult[Throwable]) => IO[Unit]): IO[B] = | ||
acquire.bracket(use)(release) | ||
} | ||
|
||
implicit val ioParallel: Parallel[IO, IO.Par] = | ||
|
@@ -776,7 +795,11 @@ object IO extends IOInstances { | |
* Lifts an Either[Throwable, A] into the IO[A] context raising the throwable | ||
* if it exists. | ||
*/ | ||
def fromEither[A](e: Either[Throwable, A]): IO[A] = e.fold(IO.raiseError, IO.pure) | ||
def fromEither[A](e: Either[Throwable, A]): IO[A] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be nice to use def fromEither[A](e: Either[Throwable, A]): IO[A] = e.fold(raiseError, pure) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We recently got rid of all internal uses of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking about it after I commented this XD Are there any benchmarks to back this decision? I'm interested in seeing how big (percentage-wise) is the performance cost since at work we are also using folds everywhere instead of pattern matching. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is an issue that includes some benchmarks: typelevel/cats#1951 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks a lot! |
||
e match { | ||
case Right(a) => pure(a) | ||
case Left(err) => raiseError(err) | ||
} | ||
|
||
/** | ||
* Asynchronous boundary described as an effectful `IO`, managed | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ package cats | |
package effect | ||
|
||
import simulacrum._ | ||
import cats.syntax.all._ | ||
import cats.data.{EitherT, IndexedStateT, OptionT, StateT, WriterT} | ||
import cats.effect.internals.{AndThen, NonFatal} | ||
|
||
|
@@ -26,7 +27,7 @@ import cats.effect.internals.{AndThen, NonFatal} | |
* in the `F[_]` context. | ||
*/ | ||
@typeclass | ||
trait Sync[F[_]] extends MonadError[F, Throwable] { | ||
trait Sync[F[_]] extends Bracket[F, Throwable] { | ||
/** | ||
* Suspends the evaluation of an `F` reference. | ||
* | ||
|
@@ -69,6 +70,20 @@ object Sync { | |
def raiseError[A](e: Throwable) = | ||
EitherT.left(Eval.now(e)) | ||
|
||
def bracket[A, B](acquire: EitherT[Eval, Throwable, A]) | ||
(use: A => EitherT[Eval, Throwable, B]) | ||
(release: (A, BracketResult[Throwable]) => EitherT[Eval, Throwable, Unit]): EitherT[Eval, Throwable, B] = { | ||
|
||
acquire.flatMap { a => | ||
EitherT(FlatMap[Eval].flatTap(use(a).value){ etb => | ||
release(a, etb match { | ||
case Left(e) => BracketResult.Error(e) | ||
case Right(_) => BracketResult.Completed | ||
}).value | ||
}) | ||
} | ||
} | ||
|
||
def flatMap[A, B](fa: EitherT[Eval, Throwable, A])(f: A => EitherT[Eval, Throwable, B]): EitherT[Eval, Throwable, B] = | ||
fa.flatMap(f) | ||
|
||
|
@@ -126,6 +141,23 @@ object Sync { | |
def raiseError[A](e: Throwable): EitherT[F, L, A] = | ||
EitherT.liftF(F.raiseError(e)) | ||
|
||
def bracket[A, B](acquire: EitherT[F, L, A]) | ||
(use: A => EitherT[F, L, B]) | ||
(release: (A, BracketResult[Throwable]) => EitherT[F, L, Unit]): EitherT[F, L, B] = { | ||
|
||
EitherT(F.bracket(acquire.value) { | ||
case Right(a) => use(a).value | ||
case e @ Left(_) => F.pure(e.asInstanceOf[Either[L, B]]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use rightCast here (a cats syntactic enrichment) which is safe. This may be safe, but each reader has to make sure you didn’t get the L type wrong. |
||
} { (ea, br) => | ||
ea match { | ||
case Right(a) => | ||
release(a, br).value.map(_ => ()) | ||
case Left(_) => | ||
F.unit // nothing to release | ||
} | ||
}) | ||
} | ||
|
||
def flatMap[A, B](fa: EitherT[F, L, A])(f: A => EitherT[F, L, B]): EitherT[F, L, B] = | ||
fa.flatMap(f) | ||
|
||
|
@@ -147,6 +179,21 @@ object Sync { | |
def raiseError[A](e: Throwable): OptionT[F, A] = | ||
OptionT.catsDataMonadErrorForOptionT[F, Throwable].raiseError(e) | ||
|
||
def bracket[A, B](acquire: OptionT[F, A]) | ||
(use: A => OptionT[F, B]) | ||
(release: (A, BracketResult[Throwable]) => OptionT[F, Unit]): OptionT[F, B] = { | ||
|
||
OptionT(F.bracket(acquire.value) { | ||
case Some(a) => use(a).value | ||
case None => F.pure[Option[B]](None) | ||
} { | ||
case (Some(a), br) => | ||
release(a, br).value.map(_ => ()) | ||
case _ => | ||
F.unit | ||
}) | ||
} | ||
|
||
def flatMap[A, B](fa: OptionT[F, A])(f: A => OptionT[F, B]): OptionT[F, B] = | ||
fa.flatMap(f) | ||
|
||
|
@@ -168,6 +215,19 @@ object Sync { | |
def raiseError[A](e: Throwable): StateT[F, S, A] = | ||
StateT.liftF(F.raiseError(e)) | ||
|
||
def bracket[A, B](acquire: StateT[F, S, A]) | ||
(use: A => StateT[F, S, B]) | ||
(release: (A, BracketResult[Throwable]) => StateT[F, S, Unit]): StateT[F, S, B] = { | ||
|
||
StateT { startS => | ||
F.bracket(acquire.run(startS)) { case (s, a) => | ||
use(a).run(s) | ||
} { case ((s, a), br) => | ||
release(a, br).run(s).map(_ => ()) | ||
} | ||
} | ||
} | ||
|
||
override def map[A, B](fa: StateT[F, S, A])(f: A => B): StateT[F, S, B] = | ||
IndexedStateT.applyF[F, S, S, B](F.map(fa.runF) { safsba => | ||
AndThen(safsba).andThen(fa => F.map(fa) { case (s, a) => (s, f(a)) }) | ||
|
@@ -202,6 +262,19 @@ object Sync { | |
def raiseError[A](e: Throwable): WriterT[F, L, A] = | ||
WriterT.catsDataMonadErrorForWriterT[F, L, Throwable].raiseError(e) | ||
|
||
def bracket[A, B](acquire: WriterT[F, L, A]) | ||
(use: A => WriterT[F, L, B]) | ||
(release: (A, BracketResult[Throwable]) => WriterT[F, L, Unit]): WriterT[F, L, B] = { | ||
|
||
acquire.flatMap { a => | ||
WriterT( | ||
F.bracket(F.pure(a))(use.andThen(_.run)){ (a, res) => | ||
release(a, res).value | ||
} | ||
) | ||
} | ||
} | ||
|
||
def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = | ||
fa.flatMap(f) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Copyright (c) 2017-2018 The Typelevel Cats-effect Project Developers | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package cats | ||
package effect | ||
package laws | ||
|
||
import cats.implicits._ | ||
import cats.laws._ | ||
|
||
trait BracketLaws[F[_], E] extends MonadErrorLaws[F, E] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add a comment giving an example of why all |
||
implicit def F: Bracket[F, E] | ||
|
||
def bracketWithPureUnitIsEqvMap[A, B](fa: F[A], f: A => B) = | ||
F.bracket(fa)(a => f(a).pure[F])((_, _) => F.unit) <-> F.map(fa)(f) | ||
|
||
def bracketWithPureUnitIsEqvFlatMap[A, B](fa: F[A], f: A => F[B]) = | ||
F.bracket(fa)(f)((_, _) => F.unit) <-> F.flatMap(fa)(f) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we simplify the laws a bit and have the following and maybe a similar one for cancelation: def bracketWithFailureIsEqvToRunningRecover[A, B](a: F[A], err: E, recover: F[Unit]) =
F.bracket(a)(_ => F.raiseError[B](err))((_, _) => recover) <-> (a *> F.raiseError[B](err)).handleErrorWith { e => recover *> F.raiseError[B](err) } There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey @johnynek, I've been trying to build up the two laws you're talking about in the |
||
def bracketFailureInAcquisitionRemainsFailure[A, B](e: E, f: A => F[B], release: F[Unit]) = | ||
F.bracket(F.raiseError[A](e))(f)((_, _) => release) <-> F.raiseError(e) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we may need a law like this: def someLawName[A, B](acquire: F[A], use: A => F[B], release: F[Unit]) = {
val expected = acquire.flatMap(a => use.attempt.flatMap {
case Left(e) => release.flatMap(_ => F.raiseError(e))
case Right(a) => release.map(_ => a)
})
F.bracket(acquire)(use)(release) <-> expected
} I think it's interesting here what happens if both Also we can't observe side effects in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw, I see there are now laws in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alexandru I thought the consensus was that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, yea, you might be right. |
||
} | ||
|
||
object BracketLaws { | ||
def apply[F[_], E](implicit F0: Bracket[F, E]): BracketLaws[F, E] = new BracketLaws[F, E] { | ||
val F = F0 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,9 +21,19 @@ package laws | |
import cats.implicits._ | ||
import cats.laws._ | ||
|
||
trait SyncLaws[F[_]] extends MonadErrorLaws[F, Throwable] { | ||
trait SyncLaws[F[_]] extends BracketLaws[F, Throwable] { | ||
implicit def F: Sync[F] | ||
|
||
def bracketReleaseIsAlwaysCalled[A, B](fa: F[A], fb: F[B], g: A => A, a1: A) = { | ||
|
||
var input = a1 | ||
val update = F.delay { input = g(input) } | ||
val read = F.delay(input) | ||
|
||
F.bracket(fa)(_ => fb)((a, _) => update) *> read <-> fa *> fb *> F.pure(g(a1)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I missed the fact here that if |
||
} | ||
|
||
|
||
def delayConstantIsPure[A](a: A) = | ||
F.delay(a) <-> F.pure(a) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
* Copyright (c) 2017-2018 The Typelevel Cats-effect Project Developers | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package cats | ||
package effect | ||
package laws | ||
package discipline | ||
|
||
import cats.data._ | ||
import cats.laws.discipline._ | ||
import cats.laws.discipline.SemigroupalTests.Isomorphisms | ||
import org.scalacheck._ | ||
import Prop.forAll | ||
|
||
trait BracketTests[F[_], E] extends MonadErrorTests[F, E] { | ||
def laws: BracketLaws[F, E] | ||
|
||
def bracket[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq]( | ||
implicit | ||
ArbFA: Arbitrary[F[A]], | ||
ArbFB: Arbitrary[F[B]], | ||
ArbFC: Arbitrary[F[C]], | ||
ArbFU: Arbitrary[F[Unit]], | ||
ArbFAtoB: Arbitrary[F[A => B]], | ||
ArbFBtoC: Arbitrary[F[B => C]], | ||
ArbE: Arbitrary[E], | ||
CogenA: Cogen[A], | ||
CogenB: Cogen[B], | ||
CogenC: Cogen[C], | ||
CogenE: Cogen[E], | ||
EqFA: Eq[F[A]], | ||
EqFB: Eq[F[B]], | ||
EqFC: Eq[F[C]], | ||
EqE: Eq[E], | ||
EqFEitherEU: Eq[F[Either[E, Unit]]], | ||
EqFEitherEA: Eq[F[Either[E, A]]], | ||
EqEitherTFEA: Eq[EitherT[F, E, A]], | ||
EqFABC: Eq[F[(A, B, C)]], | ||
EqFInt: Eq[F[Int]], | ||
iso: Isomorphisms[F]): RuleSet = { | ||
new RuleSet { | ||
val name = "bracket" | ||
val bases = Nil | ||
val parents = Seq(monadError[A, B, C]) | ||
|
||
val props = Seq( | ||
"bracket with pure unit on release is eqv to map" -> forAll(laws.bracketWithPureUnitIsEqvMap[A, B] _), | ||
"bracket with failure in acquisition remains failure" -> forAll(laws.bracketFailureInAcquisitionRemainsFailure[A, B] _), | ||
"bracket with pure unit on release is eqv to flatMap" -> forAll(laws.bracketWithPureUnitIsEqvFlatMap[A, B] _) | ||
) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: get rid of extra newlines. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
} | ||
} | ||
} | ||
|
||
object BracketTests { | ||
def apply[F[_], E](implicit ev: Bracket[F, E]): BracketTests[F, E] = new BracketTests[F, E] { | ||
def laws = BracketLaws[F, E] | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be
final
.