├── .java-version ├── io └── src │ ├── main │ └── scala │ │ ├── .gitkeep │ │ └── au │ │ └── id │ │ └── tmm │ │ └── bfect │ │ └── io │ │ ├── instances │ │ ├── package.scala │ │ ├── effects │ │ │ ├── SyncInstance.scala │ │ │ └── BracketInstance.scala │ │ ├── BMEInstance.scala │ │ ├── IOInstances.scala │ │ ├── BifunctorInstance.scala │ │ └── BifunctorMonadInstance.scala │ │ ├── package.scala │ │ ├── IORuntime.scala │ │ └── IO.scala │ └── test │ └── scala │ ├── .gitkeep │ └── au │ └── id │ └── tmm │ └── bfect │ └── io │ ├── GenericError.scala │ ├── FlatMapRuntimeSpec.scala │ ├── SyncRuntimeSpec.scala │ └── EnsureRuntimeSpec.scala ├── project ├── build.properties ├── plugins.sbt ├── DependencySettings.scala ├── ProjectSettingsHelper.scala └── ScalacSettings.scala ├── .secrets ├── .gitignore ├── pubring.kbx.secret ├── secring.gpg.secret └── decrypt.sh ├── interop ├── cats │ └── src │ │ ├── main │ │ └── scala │ │ │ └── au │ │ │ └── id │ │ │ └── tmm │ │ │ └── bfect │ │ │ └── interop │ │ │ └── cats │ │ │ ├── instanceimpls │ │ │ ├── .gitkeep │ │ │ ├── eitherT │ │ │ │ ├── ExtraEffectsInstances.scala │ │ │ │ ├── DieInstance.scala │ │ │ │ ├── NowInstance.scala │ │ │ │ ├── SyncInstance.scala │ │ │ │ ├── BifunctorInstance.scala │ │ │ │ ├── TimerInstance.scala │ │ │ │ ├── AsyncInstance.scala │ │ │ │ ├── BMEInstance.scala │ │ │ │ ├── BracketInstance.scala │ │ │ │ ├── EitherTInstances.scala │ │ │ │ └── ConcurrentInstance.scala │ │ │ └── implicitconversions │ │ │ │ ├── FailureHandlingUtils.scala │ │ │ │ ├── CatsBifunctorForBfectBifunctor.scala │ │ │ │ ├── CatsTimerForBfectTimer.scala │ │ │ │ ├── CatsAsyncForBfectAsync.scala │ │ │ │ ├── CatsMonadForBfectBifunctorMonad.scala │ │ │ │ ├── CatsClockForBfectNow.scala │ │ │ │ ├── CatsMonadErrorForBfectBME.scala │ │ │ │ ├── CatsSyncForBfectSync.scala │ │ │ │ └── CatsConcurrentForBfectConcurrent.scala │ │ │ ├── implicits.scala │ │ │ └── instances.scala │ │ └── test │ │ └── scala │ │ └── au │ │ └── id │ │ └── tmm │ │ └── bfect │ │ └── interop │ │ └── cats │ │ ├── CatsLawsForBfectSpec.scala │ │ └── BfectToCatsTypeclassConversionsSpec.scala ├── zio │ └── src │ │ └── main │ │ └── scala │ │ └── au │ │ └── id │ │ └── tmm │ │ └── bfect │ │ └── interop │ │ └── zio │ │ ├── package.scala │ │ ├── DataConversions.scala │ │ └── ZioInstances.scala └── fs2 │ └── src │ └── main │ └── scala │ └── au │ └── id │ └── tmm │ └── bfect │ └── interop │ └── fs2 │ └── package.scala ├── core └── src │ ├── main │ └── scala │ │ └── au │ │ └── id │ │ └── tmm │ │ └── bfect │ │ ├── instances │ │ ├── package.scala │ │ └── EitherInstances.scala │ │ ├── BiFunctionK.scala │ │ ├── implicits.scala │ │ ├── BiInvariantK.scala │ │ ├── Fibre.scala │ │ ├── BiInvariant.scala │ │ ├── Failure.scala │ │ ├── effects │ │ ├── Now.scala │ │ ├── extra │ │ │ ├── EnvVars.scala │ │ │ ├── Console.scala │ │ │ ├── Resources.scala │ │ │ └── Calendar.scala │ │ ├── Async.scala │ │ ├── Die.scala │ │ ├── Sync.scala │ │ ├── Timer.scala │ │ ├── Bracket.scala │ │ └── Concurrent.scala │ │ ├── package.scala │ │ ├── ExitCase.scala │ │ ├── syntax.scala │ │ ├── BifunctorMonadError.scala │ │ ├── Bifunctor.scala │ │ └── BifunctorMonad.scala │ └── test │ └── scala │ └── au │ └── id │ └── tmm │ └── bfect │ ├── BiInvariantSyntaxSpec.scala │ ├── WrappedEither.scala │ ├── BifunctorMonadSyntaxSpec.scala │ ├── BifunctorMonadErrorSyntaxSpec.scala │ ├── BifunctorMonadSpec.scala │ └── BifunctorSyntaxSpec.scala ├── .gitignore ├── .editorconfig ├── .scalafmt.conf ├── .circleci └── config.yml ├── README.md ├── testing └── src │ ├── test │ └── scala │ │ └── au │ │ └── id │ │ └── tmm │ │ └── bfect │ │ └── effects │ │ └── TimerSpec.scala │ └── main │ └── scala │ └── au │ └── id │ └── tmm │ └── bfect │ └── testing │ └── BState.scala ├── LICENSE └── .gradletasknamecache /.java-version: -------------------------------------------------------------------------------- 1 | 11.0 2 | -------------------------------------------------------------------------------- /io/src/main/scala/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /io/src/test/scala/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.4.3 2 | -------------------------------------------------------------------------------- /.secrets/.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/.gitignore 3 | !/decrypt.sh 4 | !/encrypt.sh 5 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.secrets/pubring.kbx.secret: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmccarthy/bfect/HEAD/.secrets/pubring.kbx.secret -------------------------------------------------------------------------------- /.secrets/secring.gpg.secret: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmccarthy/bfect/HEAD/.secrets/secring.gpg.secret -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/instances/package.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | package object instances { 4 | object either extends EitherInstances 5 | 6 | object all extends AnyRef with EitherInstances 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | /.idea 5 | *.iml 6 | 7 | dist/* 8 | target/ 9 | lib_managed/ 10 | src_managed/ 11 | project/boot/ 12 | project/plugins/project/ 13 | .history 14 | .cache 15 | .lib/ 16 | 17 | *.gpg 18 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/BiFunctionK.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | trait BiFunctionK[F[_, _], G[_, _]] { 4 | def apply[L, R](flr: F[L, R]): G[L, R] 5 | } 6 | 7 | object BiFunctionK { 8 | 9 | trait Syntax { 10 | //noinspection NonAsciiCharacters 11 | type ≈>[F[_, _], G[_, _]] = BiFunctionK[F, G] 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.4") 2 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2") 3 | addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.2.0") 4 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.1") 5 | addSbtPlugin("ch.epfl.scala" % "sbt-release-early" % "2.1.1") 6 | // TODO add scoverage 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.{gradle,java,groovy}] 15 | indent_size = 4 16 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/ExtraEffectsInstances.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect.effects.extra._ 4 | import cats.data.EitherT 5 | 6 | class ExtraEffectsInstances[F[_] : cats.effect.Sync] private[instanceimpls] 7 | extends SyncInstance[F] 8 | with Calendar.Live[EitherT[F, *, *]] 9 | with Console.Live[EitherT[F, *, *]] 10 | with EnvVars.Live[EitherT[F, *, *]] 11 | with Resources.Live[EitherT[F, *, *]] 12 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/DieInstance.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect.effects.Die 4 | import cats.MonadError 5 | import cats.data.EitherT 6 | 7 | class DieInstance[F[_]] private[instanceimpls] (implicit F: MonadError[F, Throwable]) 8 | extends BMEInstance[F] 9 | with Die[EitherT[F, *, *]] { 10 | override def failUnchecked(t: Throwable): EitherT[F, Nothing, Nothing] = 11 | EitherT.liftF[F, Nothing, Nothing](F.raiseError[Nothing](t)) 12 | } 13 | -------------------------------------------------------------------------------- /project/DependencySettings.scala: -------------------------------------------------------------------------------- 1 | import sbt.Keys.libraryDependencies 2 | import sbt._ 3 | import sbt.librarymanagement.{CrossVersion, ModuleID} 4 | 5 | object DependencySettings { 6 | 7 | val commonDependencies: Seq[Def.Setting[Seq[ModuleID]]] = Seq( 8 | libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.3" % Test, 9 | libraryDependencies += "com.github.ghik" %% "silencer-lib" % "1.7.1" % Provided cross CrossVersion.full, 10 | libraryDependencies += compilerPlugin("com.github.ghik" %% "silencer-plugin" % "1.7.1" cross CrossVersion.full), 11 | ) 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.secrets/decrypt.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | KEY=$1 6 | 7 | [ -n "$KEY" ] || { echo "No key provided"; kill "$PPID"; exit 1; } 8 | 9 | SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 10 | DEST_DIR=/tmp/secrets 11 | OPENSSL_COMMAND=aes-256-cbc 12 | 13 | mkdir -p $DEST_DIR 14 | 15 | for ENCRYPTED_FILE in "$SOURCE_DIR"/*.secret; do 16 | FILE_NAME=$(basename "$ENCRYPTED_FILE") 17 | DECRYPTED_NAME=${FILE_NAME%".secret"} 18 | openssl $OPENSSL_COMMAND -d -in "$ENCRYPTED_FILE" -k "$KEY" -out "$DEST_DIR/$DECRYPTED_NAME" -md md5 19 | done 20 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/NowInstance.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect.effects.Now 4 | import cats.Functor 5 | import cats.data.EitherT 6 | 7 | import java.time.Instant 8 | 9 | class NowInstance[F[_]] private[instanceimpls] (implicit clockF: cats.effect.Clock[F], F: Functor[F]) 10 | extends Now[EitherT[F, *, *]] { 11 | override def now: EitherT[F, Nothing, Instant] = EitherT.liftF[F, Nothing, Instant] { 12 | F.map(clockF.realTime(scala.concurrent.duration.MILLISECONDS))(Instant.ofEpochMilli) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/implicits.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | object implicits 4 | extends AnyRef 5 | with BiFunctionK.Syntax 6 | with BiInvariantK.ToBiInvariantKOps 7 | with BiInvariant.ToBiInvariantOps 8 | with Bifunctor.ToBifunctorOps 9 | with BifunctorMonad.ToBifunctorMonadOps 10 | with BifunctorMonadError.ToBifunctorMonadErrorOps 11 | with effects.Die.ToDieOps 12 | with effects.Timer.ToTimerOps 13 | with effects.Sync.ToSyncOps 14 | with effects.Bracket.ToBracketOps 15 | with effects.Async.ToAsyncOps 16 | with effects.Concurrent.ToConcurrentOps 17 | with instances.EitherInstances 18 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/SyncInstance.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect.effects.Sync 4 | import cats.data.EitherT 5 | 6 | class SyncInstance[F[_] : cats.effect.Sync] private[instanceimpls] 7 | extends BracketInstance[F] 8 | with Sync[EitherT[F, *, *]] { 9 | override def failUnchecked(t: Throwable): EitherT[F, Nothing, Nothing] = 10 | EitherT.liftF[F, Nothing, Nothing](cats.effect.Sync[F].raiseError[Nothing](t)) 11 | 12 | override def suspend[E, A](effect: => EitherT[F, E, A]): EitherT[F, E, A] = 13 | EitherT { 14 | cats.effect.Sync[F].defer(effect.value) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/implicitconversions/FailureHandlingUtils.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions 2 | 3 | import au.id.tmm.bfect.effects.Sync 4 | 5 | private[implicitconversions] object FailureHandlingUtils { 6 | 7 | def makeFailureUnchecked[F[_, _], E, A](fea: F[E, A])(implicit syncInstance: Sync[F]): F[Nothing, A] = 8 | syncInstance.handleErrorWith[E, A, Nothing](fea) { 9 | case t: Throwable => syncInstance.sync(throw t) 10 | case e => syncInstance.sync(throw FailureInCancellationToken(e)) 11 | } 12 | 13 | final case class FailureInCancellationToken[E](e: E) extends Exception 14 | 15 | } 16 | -------------------------------------------------------------------------------- /core/src/test/scala/au/id/tmm/bfect/BiInvariantSyntaxSpec.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | import au.id.tmm.bfect.BiInvariantSyntaxSpec.Wrapped 4 | import au.id.tmm.bfect.syntax.biInvariant._ 5 | import org.scalatest.flatspec.AnyFlatSpec 6 | 7 | class BiInvariantSyntaxSpec extends AnyFlatSpec { 8 | 9 | "the syntax for biInvariant" should "compile" in { 10 | val either: WrappedEither[String, Int] = WrappedEither(Right(1)) 11 | 12 | either.biImap(s => Wrapped(s), i => Wrapped(i))(ws => ws.unwrap, wi => wi.unwrap): WrappedEither[ 13 | Wrapped[String], 14 | Wrapped[Int]] 15 | } 16 | 17 | } 18 | 19 | object BiInvariantSyntaxSpec { 20 | final case class Wrapped[A](unwrap: A) extends AnyVal 21 | } 22 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/implicits.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats 2 | 3 | import au.id.tmm.bfect.interop.cats.instanceimpls.eitherT.EitherTInstanceTraits0 4 | import au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions._ 5 | 6 | object implicits 7 | extends AnyRef 8 | with EitherTInstanceTraits0 9 | with CatsBifunctorForBfectBifunctor.ToCatsBifunctor 10 | with CatsMonadForBfectBifunctorMonad.ToCatsMonad 11 | with CatsMonadErrorForBfectBME.ToCatsMonadError 12 | with CatsSyncForBfectSync.ToCatsSync 13 | with CatsAsyncForBfectAsync.ToCatsAsync 14 | with CatsConcurrentForBfectConcurrent.ToCatsConcurrent 15 | with CatsClockForBfectNow.ToCatsClock 16 | with CatsTimerForBfectTimer.ToCatsTimer 17 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/BifunctorInstance.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect.Bifunctor 4 | import cats.Functor 5 | import cats.data.EitherT 6 | 7 | class BifunctorInstance[F[_]] private[instanceimpls] (implicit functor: Functor[F]) 8 | extends Bifunctor[EitherT[F, *, *]] { 9 | override def biMap[L1, R1, L2, R2](f: EitherT[F, L1, R1])(leftF: L1 => L2, rightF: R1 => R2): EitherT[F, L2, R2] = 10 | f.bimap(leftF, rightF) 11 | 12 | override def rightMap[L, R1, R2](f: EitherT[F, L, R1])(rightF: R1 => R2): EitherT[F, L, R2] = f.map(rightF) 13 | 14 | override def leftMap[L1, R, L2](f: EitherT[F, L1, R])(leftF: L1 => L2): EitherT[F, L2, R] = f.leftMap(leftF) 15 | } 16 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/BiInvariantK.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | import au.id.tmm.bfect.syntax.biFunctionK.≈> 4 | 5 | trait BiInvariantK[T[_[_, _]]] { 6 | 7 | def biImapK[F[_, _], G[_, _]](F: T[F])(fFG: F ≈> G)(fGF: G ≈> F): T[G] 8 | 9 | } 10 | 11 | object BiInvariantK { 12 | def apply[T[_[_, _]]](implicit T: BiInvariantK[T]): BiInvariantK[T] = implicitly 13 | 14 | trait ToBiInvariantKOps { 15 | implicit def toBiInvariantKOps[T[_[_, _]], F[_, _]](tf: T[F])(implicit biInvariantK: BiInvariantK[T]): Ops[T, F] = 16 | new Ops(tf) 17 | } 18 | 19 | final class Ops[T[_[_, _]], F[_, _]](tf: T[F])(implicit biInvariantK: BiInvariantK[T]) { 20 | def biImapK[G[_, _]](fFG: F ≈> G)(fGF: G ≈> F): T[G] = biInvariantK.biImapK[F, G](tf)(fFG)(fGF) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/implicitconversions/CatsBifunctorForBfectBifunctor.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions 2 | 3 | import au.id.tmm.bfect._ 4 | 5 | class CatsBifunctorForBfectBifunctor[F[_, _]](implicit bfectBifunctor: Bifunctor[F]) extends cats.Bifunctor[F] { 6 | override def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D] = bfectBifunctor.biMap(fab)(f, g) 7 | 8 | override def leftMap[A, B, C](fab: F[A, B])(f: A => C): F[C, B] = bfectBifunctor.leftMap(fab)(f) 9 | } 10 | 11 | object CatsBifunctorForBfectBifunctor { 12 | trait ToCatsBifunctor { 13 | implicit def bfectBifunctorIsCatsBifunctor[F[_, _] : Bifunctor]: cats.Bifunctor[F] = 14 | new CatsBifunctorForBfectBifunctor[F]() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /io/src/test/scala/au/id/tmm/bfect/io/GenericError.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io 17 | 18 | case object GenericError 19 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/TimerInstance.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect.effects.{Now, Timer} 4 | import cats.Monad 5 | import cats.data.EitherT 6 | 7 | import java.time.{Duration, Instant} 8 | 9 | class TimerInstance[F[_] : cats.effect.Timer : Monad] private[instanceimpls] 10 | extends BMEInstance[F] 11 | with Timer.WithBMonad[EitherT[F, *, *]] { 12 | private val bfectNowInstance: Now[EitherT[F, *, *]] = new NowInstance[F] 13 | 14 | override def now: EitherT[F, Nothing, Instant] = bfectNowInstance.now 15 | 16 | override def sleep(duration: Duration): EitherT[F, Nothing, Unit] = 17 | EitherT.liftF[F, Nothing, Unit](cats.effect.Timer[F].sleep(Timer.convertJavaDurationToScalaDuration(duration))) 18 | } 19 | -------------------------------------------------------------------------------- /interop/zio/src/main/scala/au/id/tmm/bfect/interop/zio/package.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.interop 17 | 18 | package object zio extends ZioInstances 19 | -------------------------------------------------------------------------------- /io/src/main/scala/au/id/tmm/bfect/io/instances/package.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io 17 | 18 | package object instances extends FirstPriorityIOInstances 19 | -------------------------------------------------------------------------------- /io/src/main/scala/au/id/tmm/bfect/io/package.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect 17 | 18 | package object io { 19 | type IOFibre[E, A] = Fibre[IO, E, A] 20 | } 21 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/Fibre.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect 17 | 18 | trait Fibre[F[_, _], E, A] { 19 | 20 | def cancel: F[Nothing, Unit] 21 | 22 | def join: F[E, A] 23 | 24 | } 25 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/AsyncInstance.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect.effects.Async 4 | import cats.data.EitherT 5 | 6 | class AsyncInstance[F[_] : cats.effect.Async] private[instanceimpls] 7 | extends SyncInstance[F] 8 | with Async[EitherT[F, *, *]] { 9 | override def asyncF[E, A](registerForBfect: (Either[E, A] => Unit) => EitherT[F, Nothing, _]): EitherT[F, E, A] = { 10 | val registerForCats: (Either[Throwable, Either[E, A]] => Unit) => F[Unit] = { 11 | cbForCats: (Either[Throwable, Either[E, A]] => Unit) => 12 | val cbForBfect: Either[E, A] => Unit = either => cbForCats(Right(either)) 13 | 14 | cats.effect.Async[F].as(registerForBfect(cbForBfect).value, ()) 15 | } 16 | 17 | EitherT(cats.effect.Async[F].asyncF[Either[E, A]](registerForCats)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "2.0.0" 2 | 3 | maxColumn = 120 4 | 5 | align = more 6 | danglingParentheses = false 7 | continuationIndent.defnSite = 2 8 | 9 | spaces { 10 | inImportCurlyBraces = false 11 | beforeContextBoundColon = true 12 | } 13 | 14 | verticalMultiline { 15 | atDefnSite = true 16 | newlineAfterOpenParen = true 17 | arityThreshold = 3 18 | excludeDanglingParens = [] 19 | newlineAfterImplicitKW = true 20 | } 21 | 22 | optIn { 23 | annotationNewlines = true 24 | breaksInsideChains = false 25 | breakChainOnFirstMethodDot = true 26 | } 27 | 28 | includeNoParensInSelectChains = false 29 | 30 | trailingCommas = always 31 | 32 | rewrite.rules = [RedundantBraces, RedundantParens, SortModifiers, PreferCurlyFors] 33 | rewrite.sortModifiers.order = [ 34 | "`override`" 35 | "`private`" 36 | "`protected`" 37 | "`final`" 38 | "`sealed`" 39 | "`abstract`" 40 | "`implicit`" 41 | "`lazy`" 42 | ] 43 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/implicitconversions/CatsTimerForBfectTimer.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions 2 | 3 | import au.id.tmm.bfect.BFunctor 4 | import au.id.tmm.bfect.effects.Timer 5 | import cats.effect.Clock 6 | 7 | import scala.concurrent.duration.FiniteDuration 8 | 9 | class CatsTimerForBfectTimer[F[_, _]](implicit bfectTimer: Timer[F], bFunctor: BFunctor[F]) 10 | extends CatsClockForBfectNow[F] 11 | with cats.effect.Timer[F[Throwable, *]] { 12 | override def clock: Clock[F[Throwable, *]] = this 13 | 14 | override def sleep(duration: FiniteDuration): F[Throwable, Unit] = bFunctor.leftWiden(bfectTimer.sleep(duration)) 15 | } 16 | 17 | object CatsTimerForBfectTimer { 18 | trait ToCatsTimer { 19 | implicit def bfectTimerIsCatsTimer[F[_, _] : Timer : BFunctor]: cats.effect.Timer[F[Throwable, *]] = 20 | new CatsTimerForBfectTimer[F]() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/implicitconversions/CatsAsyncForBfectAsync.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions 2 | 3 | import au.id.tmm.bfect.effects.{Async, Bracket} 4 | 5 | class CatsAsyncForBfectAsync[F[_, _]](implicit bfectBracket: Bracket[F], bfectAsync: Async[F]) 6 | extends CatsSyncForBfectSync[F] 7 | with cats.effect.Async[F[Throwable, *]] { 8 | override def async[A](k: (Either[Throwable, A] => Unit) => Unit): F[Throwable, A] = bfectAsync.async(k) 9 | override def asyncF[A](k: (Either[Throwable, A] => Unit) => F[Throwable, Unit]): F[Throwable, A] = 10 | bfectAsync.asyncF[Throwable, A](k.andThen(FailureHandlingUtils.makeFailureUnchecked(_))) 11 | } 12 | 13 | object CatsAsyncForBfectAsync { 14 | trait ToCatsAsync { 15 | implicit def bfectAsyncIsCatsAsync[F[_, _] : Async : Bracket]: cats.effect.Async[F[Throwable, *]] = 16 | new CatsAsyncForBfectAsync[F]() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/BiInvariant.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | trait BiInvariant[F[_, _]] { 4 | 5 | def biImap[L1, L2, R1, R2]( 6 | fl1r1: F[L1, R1], 7 | )( 8 | fl1l2: L1 => L2, 9 | fr1r2: R1 => R2, 10 | )( 11 | fl2l1: L2 => L1, 12 | fr2r1: R2 => R1, 13 | ): F[L2, R2] 14 | 15 | } 16 | 17 | object BiInvariant { 18 | 19 | def apply[F[_, _] : BiInvariant]: BiInvariant[F] = implicitly 20 | 21 | trait ToBiInvariantOps { 22 | implicit def toBiInvariantOps[F[_, _], L1, R1]( 23 | fl1r1: F[L1, R1], 24 | )(implicit 25 | biInvariant: BiInvariant[F], 26 | ): Ops[F, L1, R1] = 27 | new Ops[F, L1, R1](fl1r1) 28 | } 29 | 30 | final class Ops[F[_, _], L1, R1](fl1r1: F[L1, R1])(implicit biInvariant: BiInvariant[F]) { 31 | def biImap[L2, R2](fl1l2: L1 => L2, fr1r2: R1 => R2)(fl2l1: L2 => L1, fr2r1: R2 => R1): F[L2, R2] = 32 | biInvariant.biImap(fl1r1)(fl1l2, fr1r2)(fl2l1, fr2r1) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/implicitconversions/CatsMonadForBfectBifunctorMonad.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions 2 | 3 | import au.id.tmm.bfect.BifunctorMonad 4 | 5 | class CatsMonadForBfectBifunctorMonad[F[_, _], E](implicit bfectBifunctorMonad: BifunctorMonad[F]) 6 | extends cats.Monad[F[E, *]] { 7 | override def flatMap[A, A1](fea: F[E, A])(f: A => F[E, A1]): F[E, A1] = 8 | bfectBifunctorMonad.flatMap[E, E, A, A1](fea)(f) 9 | 10 | override def tailRecM[A, A1](a: A)(f: A => F[E, Either[A, A1]]): F[E, A1] = 11 | bfectBifunctorMonad.tailRecM[E, A, A1](a)(f) 12 | 13 | override def pure[A](a: A): F[E, A] = bfectBifunctorMonad.rightPure(a) 14 | } 15 | 16 | object CatsMonadForBfectBifunctorMonad { 17 | trait ToCatsMonad { 18 | implicit def bfectBifunctorMonadIsCatsMonad[F[_, _] : BifunctorMonad, E]: cats.Monad[F[E, *]] = 19 | new CatsMonadForBfectBifunctorMonad[F, E]() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/implicitconversions/CatsClockForBfectNow.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions 2 | 3 | import au.id.tmm.bfect.BFunctor 4 | import au.id.tmm.bfect.effects.Now 5 | 6 | import scala.concurrent.duration.TimeUnit 7 | 8 | class CatsClockForBfectNow[F[_, _]](implicit bfectNow: Now[F], bFunctor: BFunctor[F]) 9 | extends cats.effect.Clock[F[Throwable, *]] { 10 | override def realTime(unit: TimeUnit): F[Throwable, Long] = 11 | bFunctor.map(bFunctor.asThrowableFallible(Now.now[F]))(_.toEpochMilli) 12 | 13 | override def monotonic(unit: TimeUnit): F[Throwable, Long] = 14 | bFunctor.map(bFunctor.asThrowableFallible(Now.now[F]))(i => i.getEpochSecond * i.getNano) 15 | } 16 | 17 | object CatsClockForBfectNow { 18 | trait ToCatsClock { 19 | implicit def bfectNowIsCatsClock[F[_, _] : Now : BFunctor]: cats.effect.Clock[F[Throwable, *]] = 20 | new CatsClockForBfectNow[F]() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /interop/cats/src/test/scala/au/id/tmm/bfect/interop/cats/CatsLawsForBfectSpec.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats 2 | 3 | import au.id.tmm.bfect.WrappedEither 4 | import cats.Eq 5 | import cats.laws.discipline.{FunctorTests, MonadErrorTests, MonadTests} 6 | import org.scalatestplus.scalacheck.Checkers 7 | import org.scalacheck.ScalacheckShapeless._ 8 | import cats.instances.all._ 9 | import org.typelevel.discipline.scalatest.FunSuiteDiscipline 10 | import au.id.tmm.bfect.interop.cats.implicits._ 11 | import org.scalatest.funsuite.AnyFunSuite 12 | 13 | class CatsLawsForBfectSpec extends AnyFunSuite with FunSuiteDiscipline with Checkers { 14 | type F[A] = WrappedEither[String, A] 15 | private implicit def eqF[A]: Eq[F[A]] = Eq.fromUniversalEquals[F[A]] 16 | checkAll("WrappedEither.FunctorLaws", FunctorTests[F].functor[Int, Boolean, String]) 17 | checkAll("WrappedEither.MonadLaws", MonadTests[F].monad[Int, Boolean, String]) 18 | checkAll("WrappedEither.MonadErrorLaws", MonadErrorTests[F, String].monadError[Int, Boolean, String]) 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/Failure.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect 17 | 18 | sealed trait Failure[+E] 19 | 20 | object Failure { 21 | case class Checked[E](e: E) extends Failure[E] 22 | case class Unchecked(exception: Throwable) extends Failure[Nothing] 23 | case object Interrupted extends Failure[Nothing] 24 | } 25 | -------------------------------------------------------------------------------- /io/src/main/scala/au/id/tmm/bfect/io/instances/effects/SyncInstance.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io.instances.effects 17 | 18 | import au.id.tmm.bfect.io.IO 19 | import au.id.tmm.bfect.effects.Sync 20 | 21 | class SyncInstance private[instances] () extends BracketInstance with Sync[IO] { 22 | override def suspend[E, A](effect: => IO[E, A]): IO[E, A] = IO.sync(effect).flatten 23 | } 24 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/BMEInstance.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect.BifunctorMonadError 4 | import cats.Monad 5 | import cats.data.EitherT 6 | 7 | class BMEInstance[F[_]] private[instanceimpls] (implicit monad: Monad[F]) 8 | extends BifunctorInstance[F] 9 | with BifunctorMonadError[EitherT[F, *, *]] { 10 | override def flatMap[E1, E2 >: E1, A, B]( 11 | fe1a: EitherT[F, E1, A], 12 | )( 13 | fafe2b: A => EitherT[F, E2, B], 14 | ): EitherT[F, E2, B] = fe1a.flatMap(fafe2b) 15 | 16 | override final def tailRecM[E, A, A1](a: A)(f: A => EitherT[F, E, Either[A, A1]]): EitherT[F, E, A1] = 17 | Monad[EitherT[F, E, *]].tailRecM(a)(f) 18 | 19 | def rightPure[E, A](a: A): EitherT[F, E, A] = EitherT.rightT[F, E](a) 20 | 21 | def leftPure[E, A](e: E): EitherT[F, E, A] = EitherT.leftT[F, A](e) 22 | 23 | override def handleErrorWith[E1, A, E2](fea: EitherT[F, E1, A])(f: E1 => EitherT[F, E2, A]): EitherT[F, E2, A] = 24 | fea.leftFlatMap(f) 25 | } 26 | -------------------------------------------------------------------------------- /io/src/main/scala/au/id/tmm/bfect/io/instances/BMEInstance.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io.instances 17 | 18 | import au.id.tmm.bfect.io.IO 19 | import au.id.tmm.bfect.BME 20 | 21 | class BMEInstance private[instances] () extends BifunctorMonadInstance with BME[IO] { 22 | override def handleErrorWith[E1, A, E2](fea: IO[E1, A])(f: E1 => IO[E2, A]): IO[E2, A] = fea.foldM(f, IO.pure) 23 | } 24 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/implicitconversions/CatsMonadErrorForBfectBME.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions 2 | 3 | import au.id.tmm.bfect.BME 4 | 5 | class CatsMonadErrorForBfectBME[F[_, _], E](implicit bfectBme: BME[F]) extends cats.MonadError[F[E, *], E] { 6 | override def flatMap[A, A1](fea: F[E, A])(f: A => F[E, A1]): F[E, A1] = bfectBme.flatMap[E, E, A, A1](fea)(f) 7 | 8 | override def tailRecM[A, A1](a: A)(f: A => F[E, Either[A, A1]]): F[E, A1] = bfectBme.tailRecM[E, A, A1](a)(f) 9 | 10 | override def pure[A](a: A): F[E, A] = bfectBme.rightPure(a) 11 | 12 | override def raiseError[A](e: E): F[E, A] = bfectBme.leftPure(e) 13 | 14 | override def handleErrorWith[A](fea: F[E, A])(f: E => F[E, A]): F[E, A] = bfectBme.handleErrorWith(fea)(f) 15 | } 16 | 17 | object CatsMonadErrorForBfectBME { 18 | trait ToCatsMonadError { 19 | implicit def bfectBifunctorMonadErrorIsCatsMonadError[F[_, _] : BME, E]: cats.MonadError[F[E, *], E] = 20 | new CatsMonadErrorForBfectBME[F, E]() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/Now.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects 17 | 18 | import java.time.Instant 19 | 20 | trait Now[F[_, _]] { 21 | 22 | def now: F[Nothing, Instant] 23 | 24 | } 25 | 26 | object Now extends NowStaticOps { 27 | 28 | def apply[F[_, _] : Now]: Now[F] = implicitly[Now[F]] 29 | 30 | } 31 | 32 | trait NowStaticOps { 33 | def now[F[_, _] : Now]: F[Nothing, Instant] = Now[F].now 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/package.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm 17 | 18 | package object bfect { 19 | 20 | type BFunctor[F[_, _]] = Bifunctor[F] 21 | val BFunctor: Bifunctor.type = Bifunctor 22 | 23 | type BMonad[F[_, _]] = BifunctorMonad[F] 24 | val BMonad: BifunctorMonad.type = BifunctorMonad 25 | 26 | type BME[F[_, _]] = BifunctorMonadError[F] 27 | val BME: BifunctorMonadError.type = BifunctorMonadError 28 | 29 | } 30 | -------------------------------------------------------------------------------- /core/src/test/scala/au/id/tmm/bfect/WrappedEither.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | import au.id.tmm.bfect.instances.either._ 4 | import au.id.tmm.bfect.syntax.biFunctionK.≈> 5 | import au.id.tmm.bfect.syntax.biInvariantK._ 6 | 7 | /** 8 | * A thin wrapper around `Either` that exposes no methods. We use this in the syntax tests 9 | * to ensure that (for example) when we call `.map` we are confident that we are invoking the method provided by the 10 | * syntax import, rather than the `.map` provided by `Either`. 11 | */ 12 | final case class WrappedEither[+L, +R](either: Either[L, R]) extends AnyVal 13 | 14 | object WrappedEither { 15 | implicit val bmeInstance: BME[WrappedEither] = { 16 | val wrap: Either ≈> WrappedEither = new (Either ≈> WrappedEither) { 17 | override def apply[L, R](flr: Either[L, R]): WrappedEither[L, R] = WrappedEither(flr) 18 | } 19 | 20 | val unwrap: WrappedEither ≈> Either = new (WrappedEither ≈> Either) { 21 | override def apply[L, R](flr: WrappedEither[L, R]): Either[L, R] = flr.either 22 | } 23 | 24 | BifunctorMonadError[Either].biImapK[WrappedEither](wrap)(unwrap) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/ExitCase.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect 17 | 18 | sealed trait ExitCase[+E, +A] { 19 | def as[A2](a2: A2): ExitCase[E, A2] = this match { 20 | case ExitCase.Succeeded(a) => ExitCase.Succeeded(a2) 21 | case f @ ExitCase.Failed(_) => f 22 | } 23 | } 24 | 25 | object ExitCase { 26 | 27 | final case class Succeeded[A](a: A) extends ExitCase[Nothing, A] 28 | final case class Failed[E](failure: Failure[E]) extends ExitCase[E, Nothing] 29 | 30 | } 31 | -------------------------------------------------------------------------------- /core/src/test/scala/au/id/tmm/bfect/BifunctorMonadSyntaxSpec.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | import au.id.tmm.bfect.syntax.bifunctorMonad._ 4 | 5 | class BifunctorMonadSyntaxSpec[F[_, _] : BifunctorMonad] { 6 | 7 | private def makeF[L, R]: F[L, R] = ??? 8 | 9 | { 10 | makeF[Int, String].flatMap(s => makeF[Int, Byte]): F[Int, Byte] 11 | 12 | makeF[Int, String] >> (_ => makeF[Int, Byte]): F[Int, Byte] 13 | 14 | lazy val _ = makeF[Int, String].forever 15 | 16 | makeF[Int, F[Int, String]].flatten: F[Int, String] 17 | 18 | makeF[String, Either[CharSequence, Int]].absolve: F[CharSequence, Int] 19 | 20 | makeF[String, Option[Int]].absolveOption(ifNone = "asdf"): F[String, Int] 21 | } 22 | 23 | { 24 | makeF[Nothing, String].flatMap(s => makeF[Int, Byte]): F[Int, Byte] 25 | 26 | lazy val _ = makeF[Nothing, String].forever 27 | 28 | makeF[Nothing, Either[String, Int]].absolve: F[String, Int] 29 | 30 | makeF[Nothing, Option[Int]].absolveOption(ifNone = "asdf"): F[String, Int] 31 | } 32 | 33 | { 34 | lazy val _ = makeF[Int, Nothing].forever 35 | } 36 | 37 | { 38 | lazy val _ = (??? : F[Nothing, Nothing]).forever 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /interop/fs2/src/main/scala/au/id/tmm/bfect/interop/fs2/package.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.interop 17 | 18 | import _root_.fs2.Stream 19 | import au.id.tmm.bfect.effects.{Bracket, Sync} 20 | 21 | package object fs2 { 22 | 23 | type Fs2Compiler[F[+_, +_]] = Stream.Compiler[F[Throwable, +*], F[Throwable, +*]] 24 | 25 | implicit def fs2CompilerForBfect[F[+_, +_] : Sync : Bracket]: Fs2Compiler[F] = 26 | Stream.Compiler.syncInstance[F[Throwable, +*]](cats.instances.sync.bfectSyncIsCatsSync) 27 | 28 | } 29 | -------------------------------------------------------------------------------- /io/src/main/scala/au/id/tmm/bfect/io/instances/IOInstances.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io.instances 17 | 18 | trait FirstPriorityIOInstances extends SecondPriorityIOInstances { 19 | implicit val bmeInstance: BMEInstance = new BMEInstance() 20 | } 21 | 22 | trait SecondPriorityIOInstances extends ThirdPriorityIOInstances { 23 | implicit val biFunctorMonadInstance: BifunctorMonadInstance = new BifunctorMonadInstance() 24 | } 25 | 26 | trait ThirdPriorityIOInstances { 27 | implicit val biFunctorInstance: BifunctorInstance = new BifunctorInstance() 28 | } 29 | -------------------------------------------------------------------------------- /io/src/main/scala/au/id/tmm/bfect/io/instances/BifunctorInstance.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io.instances 17 | 18 | import au.id.tmm.bfect.io.IO 19 | import au.id.tmm.bfect.Bifunctor 20 | 21 | class BifunctorInstance private[instances] () extends Bifunctor[IO] { 22 | override def biMap[L1, R1, L2, R2](f: IO[L1, R1])(leftF: L1 => L2, rightF: R1 => R2): IO[L2, R2] = 23 | f.biMap(leftF, rightF) 24 | 25 | override def rightMap[L, R1, R2](f: IO[L, R1])(rightF: R1 => R2): IO[L, R2] = 26 | f.map(rightF) 27 | 28 | override def leftMap[L1, R, L2](f: IO[L1, R])(leftF: L1 => L2): IO[L2, R] = 29 | f.leftMap(leftF) 30 | } 31 | -------------------------------------------------------------------------------- /interop/zio/src/main/scala/au/id/tmm/bfect/interop/zio/DataConversions.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.interop.zio 17 | 18 | import au.id.tmm.bfect.{Failure => BfectFailure} 19 | 20 | object DataConversions { 21 | 22 | def zioCauseToBfectFailure[E](zioFailure: zio.Cause[E]): BfectFailure[E] = 23 | zioFailure.fold[BfectFailure[E]]( 24 | empty = ???, 25 | failCase = BfectFailure.Checked(_), 26 | dieCase = BfectFailure.Unchecked(_), 27 | interruptCase = _ => BfectFailure.Interrupted, 28 | )( 29 | thenCase = (left, right) => left, 30 | bothCase = (left, right) => left, 31 | tracedCase = (failure, trace) => failure, 32 | ) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/syntax.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | import au.id.tmm.bfect.effects._ 4 | 5 | object syntax { 6 | 7 | object biFunctionK extends BiFunctionK.Syntax 8 | 9 | object biInvariantK extends BiInvariantK.ToBiInvariantKOps 10 | object biInvariant extends BiInvariant.ToBiInvariantOps 11 | object bifunctor extends Bifunctor.ToBifunctorOps 12 | object bifunctorMonad extends BifunctorMonad.ToBifunctorMonadOps 13 | object bifunctorMonadError extends BifunctorMonadError.ToBifunctorMonadErrorOps 14 | 15 | object die extends Die.ToDieOps 16 | object timer extends Timer.ToTimerOps 17 | object sync extends Sync.ToSyncOps 18 | object bracket extends Bracket.ToBracketOps 19 | object async extends Async.ToAsyncOps 20 | object concurrent extends Concurrent.ToConcurrentOps 21 | 22 | object all 23 | extends AnyRef 24 | with BiFunctionK.Syntax 25 | with BiInvariantK.ToBiInvariantKOps 26 | with BiInvariant.ToBiInvariantOps 27 | with Bifunctor.ToBifunctorOps 28 | with BifunctorMonad.ToBifunctorMonadOps 29 | with BifunctorMonadError.ToBifunctorMonadErrorOps 30 | with Die.ToDieOps 31 | with Timer.ToTimerOps 32 | with Sync.ToSyncOps 33 | with Bracket.ToBracketOps 34 | with Async.ToAsyncOps 35 | with Concurrent.ToConcurrentOps 36 | } 37 | -------------------------------------------------------------------------------- /interop/cats/src/test/scala/au/id/tmm/bfect/interop/cats/BfectToCatsTypeclassConversionsSpec.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.interop.cats 17 | 18 | import au.id.tmm.bfect.WrappedEither 19 | import org.scalatest.flatspec.AnyFlatSpec 20 | 21 | class BfectToCatsTypeclassConversionsSpec extends AnyFlatSpec { 22 | 23 | "the monad instance" should "be resolved without difficulty" in { 24 | import implicits._ 25 | 26 | cats.Monad[WrappedEither[Nothing, *]] 27 | 28 | succeed 29 | } 30 | 31 | "the MonadError instance" should "be resolved without difficulty" in { 32 | import implicits._ 33 | 34 | cats.MonadError[WrappedEither[Nothing, *], Nothing] 35 | 36 | succeed 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instances.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats 2 | 3 | import au.id.tmm.bfect.interop.cats.instanceimpls.eitherT.EitherTInstanceTraits0 4 | import au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions._ 5 | 6 | object instances { 7 | object eitherT extends EitherTInstanceTraits0 8 | 9 | object bifunctor extends CatsBifunctorForBfectBifunctor.ToCatsBifunctor 10 | object monad extends CatsMonadForBfectBifunctorMonad.ToCatsMonad 11 | object monadError extends CatsMonadErrorForBfectBME.ToCatsMonadError 12 | object sync extends CatsSyncForBfectSync.ToCatsSync 13 | object async extends CatsAsyncForBfectAsync.ToCatsAsync 14 | object concurrent extends CatsConcurrentForBfectConcurrent.ToCatsConcurrent 15 | object clock extends CatsClockForBfectNow.ToCatsClock 16 | object timer extends CatsTimerForBfectTimer.ToCatsTimer 17 | 18 | object all 19 | extends AnyRef 20 | with EitherTInstanceTraits0 21 | with CatsBifunctorForBfectBifunctor.ToCatsBifunctor 22 | with CatsMonadForBfectBifunctorMonad.ToCatsMonad 23 | with CatsMonadErrorForBfectBME.ToCatsMonadError 24 | with CatsSyncForBfectSync.ToCatsSync 25 | with CatsAsyncForBfectAsync.ToCatsAsync 26 | with CatsConcurrentForBfectConcurrent.ToCatsConcurrent 27 | with CatsClockForBfectNow.ToCatsClock 28 | with CatsTimerForBfectTimer.ToCatsTimer 29 | } 30 | -------------------------------------------------------------------------------- /io/src/test/scala/au/id/tmm/bfect/io/FlatMapRuntimeSpec.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io 17 | 18 | import au.id.tmm.bfect.ExitCase.{Failed, Succeeded} 19 | import au.id.tmm.bfect.Failure 20 | import org.scalatest.flatspec.AnyFlatSpec 21 | 22 | class FlatMapRuntimeSpec extends AnyFlatSpec { 23 | 24 | private val runtime = IORuntime() 25 | 26 | "A flatMap instance" can "run a pure flatMapped to a success" in { 27 | val io = IO 28 | .pure("hello") 29 | .flatMap(s => IO.pure(s.length)) 30 | 31 | assert(runtime.run(io) === Succeeded(5)) 32 | } 33 | 34 | it can "run a pure flatMapped to a failure" in { 35 | val io = IO 36 | .pure("hello") 37 | .flatMap(s => IO.leftPure(GenericError)) 38 | 39 | assert(runtime.run(io) === Failed(Failure.Checked(GenericError))) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /io/src/test/scala/au/id/tmm/bfect/io/SyncRuntimeSpec.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io 17 | 18 | import au.id.tmm.bfect.ExitCase.Failed 19 | import au.id.tmm.bfect.Failure 20 | import org.scalatest.flatspec.AnyFlatSpec 21 | 22 | class SyncRuntimeSpec extends AnyFlatSpec { 23 | 24 | private val runtime = IORuntime() 25 | 26 | "an IORuntime" can "run a sync action that throws" in { 27 | val exception = new RuntimeException 28 | 29 | val io = IO.sync { 30 | throw exception 31 | } 32 | 33 | assert(runtime.run(io) === Failed(Failure.Unchecked(exception))) 34 | } 35 | 36 | it can "run a fold action that changes the error type" in { 37 | val io = IO 38 | .leftPure("Error") 39 | .leftMap(s => s"Error: $s") 40 | 41 | assert(runtime.run(io) === Failed(Failure.Checked("Error: Error"))) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/BracketInstance.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect 4 | import au.id.tmm.bfect._ 5 | import cats.data.EitherT 6 | 7 | import scala.concurrent.CancellationException 8 | 9 | class BracketInstance[F[_]] private[instanceimpls] (implicit F: cats.effect.Bracket[F, Throwable]) 10 | extends DieInstance[F] 11 | with bfect.effects.Bracket.WithBMonad[EitherT[F, *, *]] { 12 | override def bracketCase[R, E, A]( 13 | acquire: EitherT[F, E, R], 14 | release: (R, ExitCase[E, Unit]) => EitherT[F, Nothing, _], 15 | use: R => EitherT[F, E, A], 16 | ): EitherT[F, E, A] = EitherT { 17 | F.bracketCase[Either[E, R], Either[E, A]]( 18 | acquire.value, 19 | )( 20 | use = { 21 | case Left(e) => F.pure(Left(e)) 22 | case Right(r) => use(r).value 23 | }, 24 | )( 25 | release = { 26 | case (Left(e), _) => F.pure(Left(e)) 27 | case (Right(r), catsExitCase) => { 28 | val bfectExitCase: ExitCase[E, Unit] = catsExitCase match { 29 | case cats.effect.ExitCase.Completed => ExitCase.Succeeded(()) 30 | case cats.effect.ExitCase.Error(e) => ExitCase.Failed(Failure.Unchecked(e)) 31 | case cats.effect.ExitCase.Canceled => ExitCase.Failed(Failure.Unchecked(new CancellationException)) 32 | } 33 | 34 | F.as(release(r, bfectExitCase).value, ()) 35 | } 36 | }, 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /io/src/main/scala/au/id/tmm/bfect/io/instances/BifunctorMonadInstance.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io.instances 17 | 18 | import au.id.tmm.bfect.io.IO 19 | import au.id.tmm.bfect.BifunctorMonad 20 | 21 | class BifunctorMonadInstance private[instances] () extends BifunctorInstance with BifunctorMonad[IO] { 22 | 23 | override def rightPure[E, A](a: A): IO[E, A] = IO.pure(a) 24 | 25 | override def leftPure[E, A](e: E): IO[E, A] = IO.leftPure(e) 26 | 27 | override def flatMap[E1, E2 >: E1, A, B](fe1a: IO[E1, A])(fafe2b: A => IO[E2, B]): IO[E2, B] = fe1a.flatMap(fafe2b) 28 | 29 | /** 30 | * Keeps calling `f` until a `scala.util.Right[B]` is returned. 31 | */ 32 | override def tailRecM[E, A, A1](a: A)(f: A => IO[E, Either[A, A1]]): IO[E, A1] = f(a).flatMap { 33 | case Right(a1) => IO.pure(a1) 34 | case Left(a) => tailRecM(a)(f) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /io/src/main/scala/au/id/tmm/bfect/io/instances/effects/BracketInstance.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io.instances.effects 17 | 18 | import au.id.tmm.bfect.ExitCase 19 | import au.id.tmm.bfect.effects.Bracket 20 | import au.id.tmm.bfect.io.IO 21 | import au.id.tmm.bfect.io.instances.BMEInstance 22 | 23 | class BracketInstance private[instances] () extends BMEInstance with Bracket[IO] { 24 | 25 | override def bracketCase[R, E, A]( 26 | acquire: IO[E, R], 27 | release: (R, ExitCase[E, Unit]) => IO[Nothing, _], 28 | use: R => IO[E, A], 29 | ): IO[E, A] = 30 | IO.bracketCase(acquire)(release)(use) 31 | 32 | override def bracket[R, E, A]( 33 | acquire: IO[E, R], 34 | release: R => IO[Nothing, _], 35 | use: R => IO[E, A], 36 | ): IO[E, A] = 37 | IO.bracket(acquire)(release)(use) 38 | 39 | override def ensure[E, A](fea: IO[E, A])(finalizer: IO[Nothing, _]): IO[E, A] = 40 | fea.ensure(finalizer) 41 | 42 | override def ensureCase[E, A](fea: IO[E, A])(finalizer: ExitCase[E, Unit] => IO[Nothing, _]): IO[E, A] = 43 | fea.ensureCase(finalizer) 44 | 45 | } 46 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/extra/EnvVars.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects.extra 17 | 18 | import au.id.tmm.bfect.BMonad 19 | import au.id.tmm.bfect.effects.Sync 20 | 21 | trait EnvVars[F[_, _]] { 22 | def envVars: F[Nothing, Map[String, String]] 23 | 24 | def envVar(key: String): F[Nothing, Option[String]] 25 | 26 | def envVarOrError[E](key: String, onMissing: => E): F[E, String] 27 | } 28 | 29 | object EnvVars { 30 | 31 | def apply[F[_, _] : EnvVars]: EnvVars[F] = implicitly[EnvVars[F]] 32 | 33 | trait WithBMonad[F[_, _]] extends EnvVars[F] { self: BMonad[F] => 34 | override def envVar(key: String): F[Nothing, Option[String]] = 35 | map[Nothing, Map[String, String], Option[String]](envVars)(_.get(key)) 36 | 37 | override def envVarOrError[E](key: String, onMissing: => E): F[E, String] = 38 | flatMap(envVars)(_.get(key).fold[F[E, String]](leftPure(onMissing))(rightPure)) 39 | } 40 | 41 | trait Live[F[_, _]] extends EnvVars.WithBMonad[F] { self: Sync[F] => 42 | override def envVars: F[Nothing, Map[String, String]] = sync(sys.env) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/implicitconversions/CatsSyncForBfectSync.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions 2 | 3 | import au.id.tmm.bfect.effects.{Bracket, Sync} 4 | import au.id.tmm.bfect.{ExitCase, Failure} 5 | 6 | class CatsSyncForBfectSync[F[_, _]](implicit bfectBracket: Bracket[F], bfectSync: Sync[F]) 7 | extends CatsMonadErrorForBfectBME[F, Throwable] 8 | with cats.effect.Sync[F[Throwable, *]] { 9 | override def suspend[A](thunk: => F[Throwable, A]): F[Throwable, A] = bfectSync.suspend(thunk) 10 | 11 | override def bracketCase[A, B]( 12 | acquire: F[Throwable, A], 13 | )( 14 | use: A => F[Throwable, B], 15 | )( 16 | release: (A, cats.effect.ExitCase[Throwable]) => F[Throwable, Unit], 17 | ): F[Throwable, B] = { 18 | 19 | val releaseForBfectBracket: (A, ExitCase[Throwable, Unit]) => F[Nothing, _] = { 20 | case (resource, exitCase) => 21 | val catsExitCase: cats.effect.ExitCase[Throwable] = exitCase match { 22 | case ExitCase.Succeeded(a) => cats.effect.ExitCase.Completed 23 | case ExitCase.Failed(Failure.Interrupted) => cats.effect.ExitCase.Canceled 24 | case ExitCase.Failed(Failure.Checked(e)) => cats.effect.ExitCase.Error(e) 25 | case ExitCase.Failed(Failure.Unchecked(t)) => cats.effect.ExitCase.Error(t) 26 | } 27 | 28 | bfectSync.handleErrorWith[Throwable, Unit, Nothing](release(resource, catsExitCase))(t => throw t) 29 | } 30 | 31 | bfectBracket.bracketCase[A, Throwable, B](acquire, releaseForBfectBracket, use) 32 | } 33 | } 34 | 35 | object CatsSyncForBfectSync { 36 | trait ToCatsSync { 37 | implicit def bfectSyncIsCatsSync[F[_, _] : Sync : Bracket]: cats.effect.Sync[F[Throwable, *]] = 38 | new CatsSyncForBfectSync[F]() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/instances/EitherInstances.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.instances 2 | 3 | import au.id.tmm.bfect.BifunctorMonadError 4 | 5 | import scala.annotation.tailrec 6 | 7 | trait EitherInstances { 8 | 9 | implicit val bfectBifunctorMonadErrorForEither: BifunctorMonadError[Either] = new EitherBMEInstance 10 | 11 | } 12 | 13 | class EitherBMEInstance extends BifunctorMonadError[Either] { 14 | override def biImap[L1, L2, R1, R2]( 15 | fl1r1: Either[L1, R1], 16 | )( 17 | fl1l2: L1 => L2, 18 | fr1r2: R1 => R2, 19 | )( 20 | fl2l1: L2 => L1, 21 | fr2r1: R2 => R1, 22 | ): Either[L2, R2] = 23 | fl1r1 match { 24 | case Left(l1) => Left(fl1l2(l1)) 25 | case Right(r1) => Right(fr1r2(r1)) 26 | } 27 | override def biMap[L1, R1, L2, R2](f: Either[L1, R1])(leftF: L1 => L2, rightF: R1 => R2): Either[L2, R2] = 28 | f.fold(leftF.andThen(Left.apply), rightF.andThen(Right.apply)) 29 | 30 | override def rightMap[L, R1, R2](f: Either[L, R1])(rightF: R1 => R2): Either[L, R2] = f.map(rightF) 31 | 32 | override def leftMap[L1, R, L2](f: Either[L1, R])(leftF: L1 => L2): Either[L2, R] = f.left.map(leftF) 33 | override def rightPure[E, A](a: A): Either[E, A] = Right(a) 34 | 35 | override def leftPure[E, A](e: E): Either[E, A] = Left(e) 36 | 37 | override def flatMap[E1, E2 >: E1, A, B](fe1a: Either[E1, A])(fafe2b: A => Either[E2, B]): Either[E2, B] = 38 | fe1a.flatMap(fafe2b) 39 | 40 | @tailrec 41 | override final def tailRecM[E, A, A1](a: A)(f: A => Either[E, Either[A, A1]]): Either[E, A1] = f(a) match { 42 | case Right(Right(a)) => rightPure(a) 43 | case Right(Left(e)) => tailRecM(e)(f) 44 | case Left(e) => Left(e) 45 | } 46 | 47 | override def handleErrorWith[E1, A, E2](fea: Either[E1, A])(f: E1 => Either[E2, A]): Either[E2, A] = fea match { 48 | case Right(a) => Right(a) 49 | case Left(e) => f(e) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/extra/Console.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects.extra 17 | 18 | import au.id.tmm.bfect.effects.Sync 19 | 20 | trait Console[F[_, _]] { 21 | 22 | def lineSeparator: String 23 | 24 | def print(string: String): F[Nothing, Unit] 25 | def println(string: String): F[Nothing, Unit] = print(string + lineSeparator) 26 | 27 | def printStdOut(string: String): F[Nothing, Unit] = print(string) 28 | def printlnStdOut(string: String): F[Nothing, Unit] = println(string) 29 | 30 | def printStdErr(string: String): F[Nothing, Unit] 31 | def printlnStdErr(string: String): F[Nothing, Unit] = print(string + lineSeparator) 32 | 33 | } 34 | 35 | object Console { 36 | def apply[F[_, _] : Console]: Console[F] = implicitly[Console[F]] 37 | 38 | trait Live[F[_, _]] extends Console[F] { self: Sync[F] => 39 | override val lineSeparator: String = System.lineSeparator() 40 | override def print(string: String): F[Nothing, Unit] = sync(scala.Console.print(string)) 41 | override def println(string: String): F[Nothing, Unit] = sync(scala.Console.println(string)) 42 | override def printStdErr(string: String): F[Nothing, Unit] = sync(scala.Console.err.print(string)) 43 | override def printlnStdErr(string: String): F[Nothing, Unit] = sync(scala.Console.err.println(string)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/implicitconversions/CatsConcurrentForBfectConcurrent.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.implicitconversions 2 | 3 | import au.id.tmm.bfect.Fibre 4 | import au.id.tmm.bfect.effects.{Async, Bracket, Concurrent} 5 | 6 | class CatsConcurrentForBfectConcurrent[F[_, _]]( 7 | implicit 8 | bfectBracket: Bracket[F], 9 | bfectAsync: Async[F], 10 | bfectConcurrent: Concurrent[F], 11 | ) extends CatsAsyncForBfectAsync 12 | with cats.effect.Concurrent[F[Throwable, *]] { 13 | override def start[A](fa: F[Throwable, A]): F[Throwable, cats.effect.Fiber[F[Throwable, *], A]] = 14 | bfectAsync.map(bfectAsync.asThrowableFallible(bfectConcurrent.start(fa)))(asCatsFiber) 15 | 16 | override def racePair[A, B]( 17 | fa: F[Throwable, A], 18 | fb: F[Throwable, B], 19 | ): F[Throwable, Either[(A, cats.effect.Fiber[F[Throwable, *], B]), (cats.effect.Fiber[F[Throwable, *], A], B)]] = 20 | bfectAsync.map(bfectConcurrent.racePair(fa, fb)) { 21 | case Left((a, bFiber)) => Left((a, asCatsFiber(bFiber))) 22 | case Right((aFiber, b)) => Right((asCatsFiber(aFiber), b)) 23 | } 24 | 25 | private def asCatsFiber[A](bfectFibre: Fibre[F, Throwable, A]): cats.effect.Fiber[F[Throwable, *], A] = 26 | new cats.effect.Fiber[F[Throwable, *], A] { 27 | override def cancel: cats.effect.CancelToken[F[Throwable, *]] = 28 | bfectAsync.asThrowableFallible(bfectFibre.cancel) 29 | 30 | override def join: F[Throwable, A] = bfectFibre.join 31 | } 32 | 33 | override def race[A, B](fa: F[Throwable, A], fb: F[Throwable, B]): F[Throwable, Either[A, B]] = 34 | bfectConcurrent.race(fa, fb) 35 | 36 | override def cancelable[A]( 37 | k: (Either[Throwable, A] => Unit) => cats.effect.CancelToken[F[Throwable, *]], 38 | ): F[Throwable, A] = bfectConcurrent.cancelable(k.andThen(FailureHandlingUtils.makeFailureUnchecked(_))) 39 | } 40 | 41 | object CatsConcurrentForBfectConcurrent { 42 | trait ToCatsConcurrent { 43 | implicit def bfectConcurrentIsCatsConcurrent[ 44 | F[_, _] : Concurrent : Async : Bracket, 45 | ]: cats.effect.Concurrent[F[Throwable, *]] = new CatsConcurrentForBfectConcurrent[F]() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/src/test/scala/au/id/tmm/bfect/BifunctorMonadErrorSyntaxSpec.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | import au.id.tmm.bfect.syntax.bifunctorMonadError._ 4 | 5 | class BifunctorMonadErrorSyntaxSpec[F[_, _] : BME] { 6 | private def makeF[L, R]: F[L, R] = ??? 7 | 8 | { 9 | makeF[String, Int].attempt: F[Nothing, Either[String, Int]] 10 | makeF[String, Int].handleErrorWith(e => makeF[CharSequence, Int]): F[CharSequence, Int] 11 | makeF[String, Int].recoverWith { 12 | case e: CharSequence => makeF[CharSequence, Int] 13 | } 14 | makeF[String, Int].catchLeft { 15 | case e: CharSequence => makeF[CharSequence, Int] 16 | } 17 | makeF[String, Int].onLeft { 18 | case e: CharSequence => makeF[Nothing, Unit] 19 | } 20 | } 21 | 22 | { 23 | makeF[Nothing, Int].attempt: F[Nothing, Either[Nothing, Int]] 24 | makeF[Nothing, Int].handleErrorWith(e => makeF[CharSequence, Int]): F[CharSequence, Int] 25 | makeF[Nothing, Int].recoverWith { 26 | case e: CharSequence => makeF[CharSequence, Int] 27 | } 28 | makeF[Nothing, Int].catchLeft { 29 | case e: CharSequence => makeF[CharSequence, Int] 30 | } 31 | makeF[Nothing, Int].onLeft { 32 | case e: CharSequence => makeF[Nothing, Unit] 33 | } 34 | } 35 | 36 | { 37 | makeF[String, Nothing].attempt: F[Nothing, Either[String, Nothing]] 38 | makeF[String, Nothing].handleErrorWith(e => makeF[CharSequence, Nothing]): F[CharSequence, Nothing] 39 | makeF[String, Nothing].recoverWith { 40 | case e: CharSequence => makeF[CharSequence, Nothing] 41 | } 42 | makeF[String, Nothing].catchLeft { 43 | case e: CharSequence => makeF[CharSequence, Nothing] 44 | } 45 | makeF[String, Nothing].onLeft { 46 | case e: CharSequence => makeF[Nothing, Unit] 47 | } 48 | } 49 | 50 | { 51 | makeF[Nothing, Nothing].attempt: F[Nothing, Either[Nothing, Nothing]] 52 | makeF[Nothing, Nothing].handleErrorWith(e => makeF[CharSequence, Nothing]): F[CharSequence, Nothing] 53 | makeF[Nothing, Nothing].recoverWith { 54 | case e: CharSequence => makeF[CharSequence, Nothing] 55 | } 56 | makeF[Nothing, Nothing].catchLeft { 57 | case e: CharSequence => makeF[CharSequence, Nothing] 58 | } 59 | makeF[Nothing, Nothing].onLeft { 60 | case e: CharSequence => makeF[Nothing, Unit] 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/EitherTInstances.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect 4 | import cats.data.EitherT 5 | import cats.{Functor, Monad} 6 | 7 | trait EitherTInstanceTraits0 extends EitherTInstanceTraits1 { 8 | implicit def bfectExtraEffectInstanceForCatsEitherT[F[_] : cats.effect.Sync]: AnyRef 9 | with bfect.effects.extra.Calendar[EitherT[F, *, *]] 10 | with bfect.effects.extra.Console[EitherT[F, *, *]] 11 | with bfect.effects.extra.EnvVars[EitherT[F, *, *]] 12 | with bfect.effects.extra.Resources[EitherT[F, *, *]] = new ExtraEffectsInstances[F] 13 | 14 | implicit def bfectTimerInstanceForCatsEitherT[ 15 | F[_] : cats.effect.Timer : Monad, 16 | ]: bfect.effects.Timer[EitherT[F, *, *]] = 17 | new TimerInstance[F] 18 | 19 | implicit def bfectBracketInstanceForCatsEitherT[F[_]]( 20 | implicit 21 | bracket: cats.effect.Bracket[F, Throwable], 22 | ): bfect.effects.Bracket[EitherT[F, *, *]] = new BracketInstance[F] 23 | 24 | implicit def bfectConcurrentInstanceForCatsEitherT[ 25 | F[_] : cats.effect.Concurrent, 26 | ]: bfect.effects.Concurrent[EitherT[F, *, *]] = 27 | new ConcurrentInstance[F] 28 | 29 | implicit def bfectAsyncInstanceForCatsEitherT[F[_] : cats.effect.Async]: bfect.effects.Async[EitherT[F, *, *]] = 30 | new AsyncInstance[F] 31 | 32 | } 33 | 34 | private[instanceimpls] trait EitherTInstanceTraits1 extends EitherTInstanceTraits2 { 35 | 36 | implicit def bfectNowInstanceForCatsEitherT[F[_] : cats.effect.Clock : Functor]: bfect.effects.Now[EitherT[F, *, *]] = 37 | new NowInstance[F] 38 | 39 | implicit def bfectSyncInstanceForCatsEitherT[F[_] : cats.effect.Sync]: bfect.effects.Sync[EitherT[F, *, *]] = 40 | new SyncInstance[F] 41 | 42 | } 43 | 44 | private[instanceimpls] trait EitherTInstanceTraits2 extends EitherTInstanceTraits3 { 45 | 46 | implicit def bfectDieInstanceForCatsEitherT[F[_]]( 47 | implicit 48 | monadError: cats.MonadError[F, Throwable], 49 | ): bfect.effects.Die[EitherT[F, *, *]] = new DieInstance[F] 50 | 51 | } 52 | 53 | private[instanceimpls] trait EitherTInstanceTraits3 extends EitherTInstanceTraits4 { 54 | 55 | implicit def bfectBmeInstanceForCatsEitherT[F[_] : Monad]: bfect.BifunctorMonadError[EitherT[F, *, *]] = 56 | new BMEInstance[F] 57 | 58 | } 59 | 60 | private[instanceimpls] trait EitherTInstanceTraits4 { 61 | 62 | implicit def bfectBifunctorInstanceForCatsEitherT[F[_] : Functor]: bfect.Bifunctor[EitherT[F, *, *]] = 63 | new BifunctorInstance[F] 64 | 65 | } 66 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/Async.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects 17 | 18 | import scala.util.Either 19 | 20 | trait Async[F[_, _]] extends Sync[F] { 21 | 22 | def async[E, A](k: (Either[E, A] => Unit) => Unit): F[E, A] = asyncF { callback => 23 | sync { 24 | k(callback) 25 | } 26 | } 27 | 28 | def asyncF[E, A](k: (Either[E, A] => Unit) => F[Nothing, _]): F[E, A] 29 | 30 | def never: F[Nothing, Nothing] = async(_ => ()) 31 | 32 | } 33 | 34 | object Async extends AsyncStaticOps { 35 | 36 | def apply[F[_, _] : Async]: Async[F] = implicitly[Async[F]] 37 | 38 | trait ToAsyncOps { 39 | implicit def toAsyncOps[F[_, _], E, A](fea: F[E, A])(implicit timerInstance: Async[F]): Ops[F, E, A] = 40 | new Ops[F, E, A](fea) 41 | 42 | implicit def toAsyncOpsErrorNothing[F[_, _], A]( 43 | fea: F[Nothing, A], 44 | )(implicit 45 | timerInstance: Async[F], 46 | ): Ops[F, Nothing, A] = 47 | new Ops[F, Nothing, A](fea) 48 | 49 | implicit def toAsyncOpsValueNothing[F[_, _], E]( 50 | fea: F[E, Nothing], 51 | )(implicit 52 | timerInstance: Async[F], 53 | ): Ops[F, E, Nothing] = 54 | new Ops[F, E, Nothing](fea) 55 | 56 | implicit def toAsyncOpsErrorNothingValueNothing[F[_, _]]( 57 | fea: F[Nothing, Nothing], 58 | )(implicit 59 | timerInstance: Async[F], 60 | ): Ops[F, Nothing, Nothing] = 61 | new Ops[F, Nothing, Nothing](fea) 62 | } 63 | 64 | final class Ops[F[_, _], E, A](fea: F[E, A])(implicit async: Async[F]) 65 | 66 | } 67 | 68 | trait AsyncStaticOps extends SyncStaticOps { 69 | def async[F[_, _] : Async, E, A](k: (Either[E, A] => Unit) => Unit): F[E, A] = Async[F].async(k) 70 | def asyncF[F[_, _] : Async, E, A](k: (Either[E, A] => Unit) => F[Nothing, _]): F[E, A] = Async[F].asyncF(k) 71 | def never[F[_, _] : Async]: F[Nothing, Nothing] = Async[F].never 72 | } 73 | -------------------------------------------------------------------------------- /core/src/test/scala/au/id/tmm/bfect/BifunctorMonadSpec.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect 17 | 18 | import au.id.tmm.bfect.BifunctorMonadSpec._ 19 | import au.id.tmm.bfect.instances.either._ 20 | import au.id.tmm.bfect.syntax.bifunctorMonad._ 21 | import org.scalatest.flatspec.AnyFlatSpec 22 | 23 | class BifunctorMonadSpec extends AnyFlatSpec { 24 | 25 | "absolve" should "absolve when the outer error type is Nothing" in { 26 | val nestedEither: Either[Nothing, Either[ChildError, Unit]] = Right(Right(())) 27 | 28 | val absolved: Either[ChildError, Unit] = nestedEither.absolve 29 | 30 | assert(absolved === Right(())) 31 | } 32 | 33 | it should "absolve when the outer error type is a parent of the inner error type" in { 34 | val nestedEither: Either[ParentError, Either[ChildError, Unit]] = Right(Right(())) 35 | 36 | val absolved: Either[ParentError, Unit] = nestedEither.absolve 37 | 38 | assert(absolved === Right(())) 39 | } 40 | 41 | "absolveOption" should "absolve when the outer error type is Nothing" in { 42 | val eitherOption: Either[Nothing, Option[Unit]] = Right(Some(())) 43 | 44 | val absolved: Either[ChildError, Unit] = eitherOption.absolveOption(ChildError.Instance) 45 | 46 | assert(absolved === Right(())) 47 | } 48 | 49 | it should "absolve when the outer error type is a parent of the supplied error type" in { 50 | val eitherOption: Either[ParentError, Option[Unit]] = Right(Some(())) 51 | 52 | val absolved: Either[ParentError, Unit] = eitherOption.absolveOption(ChildError.Instance) 53 | 54 | assert(absolved === Right(())) 55 | } 56 | 57 | it should "absolve when the outer error type is a child of the supplied error type" in { 58 | val eitherOption: Either[ChildError, Option[Unit]] = Right(Some(())) 59 | 60 | val absolved: Either[ParentError, Unit] = eitherOption.absolveOption(ParentError.Instance) 61 | 62 | assert(absolved === Right(())) 63 | } 64 | 65 | } 66 | 67 | object BifunctorMonadSpec { 68 | sealed trait ParentError 69 | 70 | object ParentError { 71 | case object Instance extends ParentError 72 | } 73 | 74 | sealed trait ChildError extends ParentError 75 | 76 | object ChildError { 77 | case object Instance extends ChildError 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /project/ProjectSettingsHelper.scala: -------------------------------------------------------------------------------- 1 | import ch.epfl.scala.sbt.release.AutoImported._ 2 | import com.typesafe.sbt.SbtPgp.autoImportImpl.{pgpPublicRing, pgpSecretRing} 3 | import sbt.Keys._ 4 | import sbt._ 5 | import xerial.sbt.Sonatype 6 | import xerial.sbt.Sonatype.GitHubHosting 7 | import xerial.sbt.Sonatype.autoImport.sonatypeProjectHosting 8 | 9 | final case class ProjectSettingsHelper private ( 10 | sonatypeProfile: String, 11 | baseProjectName: String, 12 | )( 13 | githubUser: String = "tmccarthy", 14 | githubProjectName: String = baseProjectName, 15 | githubUserFullName: String = "Timothy McCarthy", 16 | githubUserEmail: String = "ebh042@gmail.com", 17 | githubUserWebsite: String = "http://tmm.id.au", 18 | 19 | primaryScalaVersion: String = "2.13.4", // Change these in the circleci file if you change them here 20 | otherScalaVersions: List[String] = List("2.12.12"), // Change these in the circleci file if you change them here 21 | ) { 22 | 23 | def settingsForBuild = { 24 | List( 25 | Keys.aggregate in releaseEarly := false, // Workaround for https://github.com/scalacenter/sbt-release-early/issues/30 26 | Sonatype.SonatypeKeys.sonatypeProfileName := sonatypeProfile, 27 | ) ++ sbt.inThisBuild( 28 | List( 29 | addCompilerPlugin("org.typelevel" % "kind-projector" % "0.11.1" cross CrossVersion.full), // TODO upgrade this 30 | organization := sonatypeProfile + "." + baseProjectName, 31 | publishMavenStyle := true, 32 | sonatypeProjectHosting := Some(GitHubHosting(githubUser, githubProjectName, githubUserFullName, githubUserEmail)), 33 | homepage := Some(url(s"https://github.com/$githubUser/$githubProjectName")), 34 | startYear := Some(2019), 35 | licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), 36 | developers := List( 37 | Developer( 38 | githubUser, 39 | githubUserFullName, 40 | githubUserEmail, 41 | url(githubUserWebsite), 42 | ) 43 | ), 44 | scmInfo := Some(ScmInfo(url(s"https://github.com/$githubUser/$githubProjectName"), s"scm:git:https://github.com/$githubUser/$githubProjectName.git")), 45 | pgpPublicRing := file("/tmp/secrets/pubring.kbx"), 46 | pgpSecretRing := file("/tmp/secrets/secring.gpg"), 47 | releaseEarlyWith := SonatypePublisher, 48 | releaseEarlyEnableInstantReleases := false, 49 | ) 50 | ) 51 | } 52 | 53 | def settingsForRootProject = Seq( 54 | skip in publish := true, 55 | name := baseProjectName, 56 | ) 57 | 58 | def settingsForSubprojectCalled(name: String) = Seq( 59 | Keys.name := s"$baseProjectName-$name", 60 | scalaVersion := primaryScalaVersion, 61 | crossScalaVersions := Seq(primaryScalaVersion) ++ otherScalaVersions, 62 | ScalacSettings.scalacSetting, 63 | publishConfiguration := publishConfiguration.value.withOverwrite(true), 64 | publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true), 65 | ) ++ DependencySettings.commonDependencies 66 | 67 | } 68 | -------------------------------------------------------------------------------- /core/src/test/scala/au/id/tmm/bfect/BifunctorSyntaxSpec.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect 2 | 3 | import au.id.tmm.bfect.syntax.bifunctor._ 4 | import com.github.ghik.silencer.silent 5 | 6 | @silent("not used") 7 | class BifunctorSyntaxSpec[F[_, _] : Bifunctor] { 8 | 9 | { 10 | val testValue: F[Int, String] = ??? 11 | 12 | testValue.biMap(i => i + 1, s => s.tail): F[Int, String] 13 | testValue.rightMap(s => s.tail): F[Int, String] 14 | testValue.map(s => s.tail): F[Int, String] 15 | testValue.leftMap(i => i + 1): F[Int, String] 16 | testValue.mapError(i => i + 1): F[Int, String] 17 | testValue.biAs(0x00, 'x'): F[Byte, Char] 18 | testValue.rightAs('x'): F[Int, Char] 19 | testValue.as('x'): F[Int, Char] 20 | testValue.unit: F[Int, Unit] 21 | testValue.leftAs(0x00): F[Byte, String] 22 | testValue.biWiden[Any, Any]: F[Any, Any] 23 | testValue.rightWiden[CharSequence]: F[Int, CharSequence] 24 | testValue.widen[CharSequence]: F[Int, CharSequence] 25 | testValue.leftWiden[Any]: F[Any, String] 26 | } 27 | 28 | { 29 | val testValue: F[Nothing, String] = ??? 30 | 31 | testValue.rightMap(s => s.tail): F[Nothing, String] 32 | testValue.map(s => s.tail): F[Nothing, String] 33 | testValue.biWiden[Any, Any]: F[Any, Any] 34 | testValue.rightWiden[CharSequence]: F[Nothing, CharSequence] 35 | testValue.widen[CharSequence]: F[Nothing, CharSequence] 36 | testValue.leftWiden[Any]: F[Any, String] 37 | testValue.biAs(0x00, 'x'): F[Byte, Char] 38 | testValue.rightAs('x'): F[Nothing, Char] 39 | testValue.as('x'): F[Nothing, Char] 40 | testValue.unit: F[Nothing, Unit] 41 | testValue.leftAs(0x00): F[Byte, String] 42 | 43 | testValue.asThrowableFallible: F[Throwable, String] 44 | testValue.asExceptionFallible: F[Exception, String] 45 | } 46 | 47 | { 48 | val testValue: F[Int, Nothing] = ??? 49 | 50 | testValue.leftMap(i => i + 1): F[Int, Nothing] 51 | testValue.mapError(i => i + 1): F[Int, Nothing] 52 | testValue.biAs(0x00, 'x'): F[Byte, Char] 53 | testValue.rightAs('x'): F[Int, Char] 54 | testValue.as('x'): F[Int, Char] 55 | testValue.unit: F[Int, Unit] 56 | testValue.leftAs(0x00): F[Byte, Nothing] 57 | testValue.biWiden[Any, Any]: F[Any, Any] 58 | testValue.rightWiden[CharSequence]: F[Int, CharSequence] 59 | testValue.widen[CharSequence]: F[Int, CharSequence] 60 | testValue.leftWiden[Any]: F[Any, Nothing] 61 | } 62 | 63 | { 64 | val testValue: F[Nothing, Nothing] = ??? 65 | 66 | testValue.biWiden[Any, Any]: F[Any, Any] 67 | testValue.rightWiden[CharSequence]: F[Nothing, CharSequence] 68 | testValue.widen[CharSequence]: F[Nothing, CharSequence] 69 | testValue.leftWiden[Any]: F[Any, Nothing] 70 | testValue.biAs(0x00, 'x'): F[Byte, Char] 71 | testValue.rightAs('x'): F[Nothing, Char] 72 | testValue.as('x'): F[Nothing, Char] 73 | testValue.unit: F[Nothing, Unit] 74 | testValue.leftAs(0x00): F[Byte, Nothing] 75 | 76 | testValue.asThrowableFallible: F[Throwable, Nothing] 77 | testValue.asExceptionFallible: F[Exception, Nothing] 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/extra/Resources.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects.extra 17 | 18 | import java.io.{IOException, InputStream} 19 | import java.nio.charset.Charset 20 | 21 | import au.id.tmm.bfect.effects.extra.Resources.ResourceStreamError 22 | import au.id.tmm.bfect.effects.{Bracket, Sync} 23 | 24 | import scala.io.Source 25 | 26 | trait Resources[F[_, _]] { 27 | 28 | def getResourceAsStream(resourceName: String): F[Nothing, Option[InputStream]] 29 | 30 | def useResourceAsStream[E, A](resourceName: String)(use: InputStream => F[E, A]): F[ResourceStreamError[E], A] 31 | 32 | def resourceAsString( 33 | resourceName: String, 34 | charset: Charset = Charset.forName("UTF-8"), 35 | ): F[ResourceStreamError[IOException], String] 36 | 37 | } 38 | 39 | object Resources { 40 | 41 | def apply[F[_, _] : Resources]: Resources[F] = implicitly[Resources[F]] 42 | 43 | sealed trait ResourceStreamError[+E] 44 | 45 | object ResourceStreamError { 46 | case object ResourceNotFound extends ResourceStreamError[Nothing] 47 | final case class UseError[+E](cause: E) extends ResourceStreamError[E] 48 | } 49 | 50 | trait Live[F[_, _]] extends Resources[F] { self: Sync[F] with Bracket[F] => 51 | override def getResourceAsStream(resourceName: String): F[Nothing, Option[InputStream]] = 52 | sync(Option(getClass.getResourceAsStream(resourceName))) 53 | 54 | override def useResourceAsStream[E, A]( 55 | resourceName: String, 56 | )( 57 | use: InputStream => F[E, A], 58 | ): F[ResourceStreamError[E], A] = 59 | bracket[InputStream, ResourceStreamError[E], A]( 60 | acquire = { 61 | flatMap(getResourceAsStream(resourceName)) { 62 | case Some(stream) => pure(stream): F[ResourceStreamError[E], InputStream] 63 | case None => leftPure(ResourceStreamError.ResourceNotFound): F[ResourceStreamError[E], InputStream] 64 | } 65 | }, 66 | release = stream => sync(stream.close()), 67 | use = use.andThen(fea => leftMap(fea)(ResourceStreamError.UseError.apply)), 68 | ) 69 | 70 | override def resourceAsString( 71 | resourceName: String, 72 | charset: Charset = Charset.forName("UTF-8"), 73 | ): F[ResourceStreamError[IOException], String] = 74 | useResourceAsStream(resourceName) { inputStream => 75 | syncCatch(Source.fromInputStream(inputStream, charset.toString).mkString) { 76 | case e: IOException => e 77 | } 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/extra/Calendar.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects.extra 17 | 18 | import java.time._ 19 | 20 | import au.id.tmm.bfect.BMonad 21 | import au.id.tmm.bfect.effects.{Now, Sync} 22 | 23 | trait Calendar[F[_, _]] extends Now[F] { 24 | 25 | def localTimezone: F[Nothing, ZoneId] 26 | 27 | def nowInstant: F[Nothing, Instant] = now 28 | 29 | def nowZonedDateTime: F[Nothing, ZonedDateTime] 30 | def nowLocalDateTime: F[Nothing, LocalDateTime] 31 | def nowLocalDate: F[Nothing, LocalDate] 32 | def nowLocalTime: F[Nothing, LocalTime] 33 | def nowOffsetDateTime: F[Nothing, OffsetDateTime] 34 | 35 | } 36 | 37 | object Calendar { 38 | 39 | def apply[F[_, _] : Calendar]: Calendar[F] = implicitly[Calendar[F]] 40 | 41 | trait WithBMonad[F[_, _]] extends Calendar[F] { self: BMonad[F] => 42 | override def nowZonedDateTime: F[Nothing, ZonedDateTime] = 43 | flatMap[Nothing, Nothing, ZoneId, ZonedDateTime](localTimezone) { tz => 44 | map[Nothing, Instant, ZonedDateTime](now) { now => 45 | now.atZone(tz) 46 | } 47 | } 48 | 49 | override def nowLocalDateTime: F[Nothing, LocalDateTime] = 50 | map[Nothing, ZonedDateTime, LocalDateTime](nowZonedDateTime)(_.toLocalDateTime) 51 | 52 | override def nowLocalDate: F[Nothing, LocalDate] = 53 | map[Nothing, ZonedDateTime, LocalDate](nowZonedDateTime)(_.toLocalDate) 54 | 55 | override def nowLocalTime: F[Nothing, LocalTime] = 56 | map[Nothing, ZonedDateTime, LocalTime](nowZonedDateTime)(_.toLocalTime) 57 | 58 | override def nowOffsetDateTime: F[Nothing, OffsetDateTime] = 59 | map[Nothing, ZonedDateTime, OffsetDateTime](nowZonedDateTime)(_.toOffsetDateTime) 60 | } 61 | 62 | trait Live[F[_, _]] extends Calendar.WithBMonad[F] { self: Sync[F] => 63 | override def localTimezone: F[Nothing, ZoneId] = sync(ZoneId.systemDefault()) 64 | override def now: F[Nothing, Instant] = sync(Instant.now()) 65 | override def nowInstant: F[Nothing, Instant] = sync(Instant.now()) 66 | override def nowZonedDateTime: F[Nothing, ZonedDateTime] = sync(ZonedDateTime.now()) 67 | override def nowLocalDateTime: F[Nothing, LocalDateTime] = sync(LocalDateTime.now()) 68 | override def nowLocalDate: F[Nothing, LocalDate] = sync(LocalDate.now()) 69 | override def nowLocalTime: F[Nothing, LocalTime] = sync(LocalTime.now()) 70 | override def nowOffsetDateTime: F[Nothing, OffsetDateTime] = sync(OffsetDateTime.now()) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/Die.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects 17 | 18 | import au.id.tmm.bfect.{BME, BifunctorMonadErrorStaticOps} 19 | 20 | trait Die[F[_, _]] extends BME[F] { 21 | 22 | def failUnchecked(t: Throwable): F[Nothing, Nothing] 23 | 24 | def die(t: Throwable): F[Nothing, Nothing] = failUnchecked(t) 25 | 26 | def orDie[E, A](fea: F[E, A])(implicit ev: E <:< Throwable): F[Nothing, A] = 27 | handleErrorWith[E, A, Nothing](fea)(e => biWiden(die(e))) 28 | 29 | //noinspection ConvertibleToMethodValue 30 | def refineOrDie[E1, A, E2]( 31 | fea: F[E1, A], 32 | )( 33 | refinePf: PartialFunction[E1, E2], 34 | )(implicit 35 | ev: E1 <:< Throwable, 36 | ): F[E2, A] = 37 | handleErrorWith[E1, A, E2](fea) { e => 38 | refinePf.andThen(e2 => leftPure[E2, A](e2)).applyOrElse(e, (t: E1) => biWiden(die(t))) 39 | } 40 | 41 | def refineToExceptionOrDie[E, A](fea: F[E, A])(implicit ev: E <:< Throwable): F[Exception, A] = refineOrDie(fea) { 42 | case e: Exception => e 43 | } 44 | 45 | } 46 | 47 | object Die extends DieStaticOps { 48 | def apply[F[_, _] : Die]: Die[F] = implicitly[Die[F]] 49 | 50 | trait ToDieOps { 51 | implicit def toDieOps[F[_, _], E, A](fea: F[E, A])(implicit die: Die[F]): Ops[F, E, A] = 52 | new Ops[F, E, A](fea) 53 | 54 | implicit def toDieOpsErrorNothing[F[_, _], A](fea: F[Nothing, A])(implicit die: Die[F]): Ops[F, Nothing, A] = 55 | new Ops[F, Nothing, A](fea) 56 | 57 | implicit def toDieOpsValueNothing[F[_, _], E](fea: F[E, Nothing])(implicit die: Die[F]): Ops[F, E, Nothing] = 58 | new Ops[F, E, Nothing](fea) 59 | 60 | implicit def toDieOpsErrorNothingValueNothing[F[_, _]]( 61 | fea: F[Nothing, Nothing], 62 | )(implicit 63 | die: Die[F], 64 | ): Ops[F, Nothing, Nothing] = 65 | new Ops[F, Nothing, Nothing](fea) 66 | } 67 | 68 | final class Ops[F[_, _], E, A](fea: F[E, A])(implicit die: Die[F]) { 69 | def orDie(implicit ev: E <:< Throwable): F[Nothing, A] = die.orDie(fea) 70 | def refineOrDie[E2](refinePf: PartialFunction[E, E2])(implicit ev: E <:< Throwable): F[E2, A] = 71 | die.refineOrDie[E, A, E2](fea)(refinePf) 72 | def refineToExceptionOrDie(implicit ev: E <:< Throwable): F[Exception, A] = die.refineToExceptionOrDie(fea) 73 | } 74 | } 75 | 76 | trait DieStaticOps extends BifunctorMonadErrorStaticOps { 77 | def failUnchecked[F[_, _] : Die](t: Throwable): F[Nothing, Nothing] = Die[F].failUnchecked(t) 78 | def die[F[_, _] : Die](t: Throwable): F[Nothing, Nothing] = Die[F].die(t) 79 | } 80 | -------------------------------------------------------------------------------- /io/src/main/scala/au/id/tmm/bfect/io/IORuntime.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io 17 | 18 | import au.id.tmm.bfect.io.IO._ 19 | import au.id.tmm.bfect.ExitCase._ 20 | import au.id.tmm.bfect.Failure.{Checked, Interrupted, Unchecked} 21 | import au.id.tmm.bfect.{ExitCase, Failure} 22 | 23 | import scala.annotation.tailrec 24 | 25 | class IORuntime private () { 26 | 27 | @tailrec 28 | final def run[E, A](io: IO[E, A]): ExitCase[E, A] = io match { 29 | case Pure(a) => Succeeded(a) 30 | case Fail(failure) => Failed(failure) 31 | 32 | case FlatMap(Pure(a), f) => run(f.asInstanceOf[Any => IO[E, A]](a)) 33 | case FlatMap(Fail(failure), _) => Failed(failure) 34 | case FlatMap(FlatMap(baseIO, f1), f2) => 35 | run(baseIO.flatMap((a: Any) => FlatMap(f2.asInstanceOf[Any => IO[E, A]](a), f1)).asInstanceOf[IO[E, A]]) 36 | case FlatMap(baseIO, f) => 37 | nonTailRecRun(baseIO) match { 38 | case Succeeded(a) => run(f.asInstanceOf[Any => IO[E, A]](a)) 39 | case Failed(failure) => Failed(failure) 40 | } 41 | 42 | case FoldM(Pure(a), leftF, rightF) => run(rightF.asInstanceOf[Any => IO[E, A]](a)) 43 | case FoldM(Fail(failure), leftF, rightF) => run(leftF.asInstanceOf[Any => IO[E, A]](failure)) 44 | case FoldM(FoldM(baseIO, leftF1, rightF1), leftF2, rightF2) => 45 | run( 46 | baseIO 47 | .foldCauseM( 48 | (a: Any) => FoldM(leftF2.asInstanceOf[Any => IO[E, A]](a), leftF1, rightF1), 49 | (a: Any) => FoldM(rightF2.asInstanceOf[Any => IO[E, A]](a), leftF1, rightF1), 50 | ) 51 | .asInstanceOf[IO[E, A]], 52 | ) 53 | case FoldM(baseIO, leftF, rightF) => 54 | nonTailRecRun(baseIO) match { 55 | case Succeeded(a) => run(rightF.asInstanceOf[Any => IO[E, A]](a)) 56 | case Failed(failure) => run(leftF.asInstanceOf[Any => IO[E, A]](failure)) 57 | } 58 | 59 | case Effect(block) => 60 | try Succeeded(block()) 61 | catch { 62 | case t: Throwable => Failed(Failure.Unchecked(t)) 63 | } 64 | 65 | case Ensure(io, finalizer) => { 66 | val result: ExitCase[E, A] = try { 67 | nonTailRecRun(io) 68 | } catch { 69 | case t: Throwable => Failed(Unchecked(t)) 70 | } 71 | 72 | nonTailRecRun(finalizer.asInstanceOf[Any => IO[Nothing, _]](result)) match { 73 | case Succeeded(_) => result 74 | case Failed(Checked(e)) => Failed(Unchecked(e)) // This is impossible 75 | case Failed(Interrupted) => Failed(Interrupted) 76 | case Failed(Unchecked(t)) => Failed(Unchecked(t)) 77 | } 78 | } 79 | } 80 | 81 | private def nonTailRecRun[E, A](io: IO[E, A]): ExitCase[E, A] = run(io) 82 | 83 | } 84 | 85 | object IORuntime { 86 | def apply(): IORuntime = new IORuntime() 87 | } 88 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.0 2 | 3 | use_jdk_8: &use_jdk_8 4 | docker: 5 | - image: circleci/openjdk:8-jdk 6 | 7 | use_jdk_11: &use_jdk_11 8 | docker: 9 | - image: circleci/openjdk:11.0.2-jdk 10 | 11 | use_scala_2_12: &use_scala_2_12 12 | environment: 13 | SCALA_VERSION: 2.12.12 14 | 15 | use_scala_2_13: &use_scala_2_13 16 | environment: 17 | SCALA_VERSION: 2.13.4 18 | 19 | use_environment: &use_environment 20 | working_directory: ~/repo 21 | 22 | environment: 23 | JVM_OPTS: -Xmx3200m 24 | TERM: dumb 25 | 26 | load_cache_step: &load_cache_step 27 | - restore_cache: 28 | keys: 29 | - v1-dependencies-{{ .Revision }} 30 | # fallback to using the latest cache if no exact match is found 31 | - v1-dependencies- 32 | paths: 33 | - "~/.ivy2/cache" 34 | - "~/.sbt" 35 | - "~/.m2" 36 | 37 | update_sbt_dependencies_step: &update_sbt_dependencies_step 38 | - run: 39 | name: Update SBT dependencies 40 | command: cat /dev/null | ./sbt +update 41 | 42 | save_cache_step: &save_cache_step 43 | - save_cache: 44 | paths: 45 | - "~/.ivy2/cache" 46 | - "~/.sbt" 47 | - "~/.m2" 48 | key: v1-dependencies-{{ .Revision }} 49 | 50 | run_tests_step: &run_tests_step 51 | - run: 52 | name: Run Tests 53 | command: cat /dev/null | ./sbt check 54 | 55 | git_fetch_tags_step: &git_fetch_tags_step 56 | - run: 57 | name: Fetch git tags 58 | command: git fetch --tags 59 | 60 | decrypt_secrets_step: &decrypt_secrets_step 61 | - run: 62 | name: Decrypt secrets 63 | command: ./.secrets/decrypt.sh "${AES_KEY}" 64 | 65 | release_step: &release_step 66 | - run: 67 | name: Release to Sonatype 68 | command: ./sbt ++${SCALA_VERSION}! releaseEarly 69 | 70 | test: &test 71 | steps: 72 | - checkout 73 | - <<: *load_cache_step 74 | - <<: *update_sbt_dependencies_step 75 | - <<: *save_cache_step 76 | - <<: *run_tests_step 77 | 78 | release: &release 79 | steps: 80 | - checkout 81 | - <<: *git_fetch_tags_step 82 | - <<: *load_cache_step 83 | - <<: *decrypt_secrets_step 84 | - <<: *release_step 85 | 86 | jobs: 87 | test_jdk_8: 88 | <<: *use_jdk_8 89 | <<: *use_environment 90 | <<: *test 91 | 92 | test_jdk_11: 93 | <<: *use_jdk_11 94 | <<: *use_environment 95 | <<: *test 96 | 97 | release_scala_2_12: 98 | <<: *use_jdk_8 99 | <<: *use_scala_2_12 100 | <<: *use_environment 101 | <<: *release 102 | 103 | release_scala_2_13: 104 | <<: *use_jdk_8 105 | <<: *use_scala_2_13 106 | <<: *use_environment 107 | <<: *release 108 | 109 | workflows: 110 | version: 2 111 | build: 112 | jobs: 113 | - test_jdk_8: 114 | filters: 115 | tags: 116 | only: /.*/ 117 | - test_jdk_11: 118 | filters: 119 | tags: 120 | only: /.*/ 121 | - release_scala_2_13: 122 | context: Sonatype 123 | requires: 124 | - test_jdk_8 125 | - test_jdk_11 126 | filters: 127 | tags: 128 | only: /^v\d+\.\d+\.\d+$/ 129 | branches: 130 | ignore: /.*/ 131 | - release_scala_2_12: 132 | context: Sonatype 133 | requires: 134 | - test_jdk_8 135 | - test_jdk_11 136 | - release_scala_2_13 137 | filters: 138 | tags: 139 | only: /^v\d+\.\d+\.\d+$/ 140 | branches: 141 | ignore: /.*/ 142 | -------------------------------------------------------------------------------- /project/ScalacSettings.scala: -------------------------------------------------------------------------------- 1 | import sbt.Keys 2 | import sbt.librarymanagement.CrossVersion 3 | 4 | object ScalacSettings { 5 | 6 | private val scalacOptionsCommon = Seq( 7 | "-deprecation", // Emit warning and location for usages of deprecated APIs. 8 | "-encoding", "utf-8", // Specify character encoding used by source files. 9 | "-explaintypes", // Explain type errors in more detail. 10 | "-feature", // Emit warning and location for usages of features that should be imported explicitly. 11 | "-language:existentials", // Existential types (besides wildcard types) can be written and inferred 12 | "-language:higherKinds", // Allow higher-kinded types 13 | "-language:implicitConversions", // Allow implicit conversions 14 | "-unchecked", // Enable additional warnings where generated code depends on assumptions. 15 | "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access. 16 | "-Xfatal-warnings", // Fail the compilation if there are any warnings. 17 | "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error. 18 | "-Xlint:delayedinit-select", // Selecting member of DelayedInit. 19 | "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element. 20 | "-Xlint:inaccessible", // Warn about inaccessible types in method signatures. 21 | "-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`. 22 | "-Xlint:nullary-unit", // Warn when nullary methods return Unit. 23 | "-Xlint:option-implicit", // Option.apply used implicit view. 24 | "-Xlint:package-object-classes", // Class or object defined in package object. 25 | "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds. 26 | "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field. 27 | "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component. 28 | "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope. 29 | "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined. 30 | "-Ywarn-unused:imports", // Warn if an import selector is not referenced. 31 | "-Ywarn-unused:locals", // Warn if a local definition is unused. 32 | "-Ywarn-unused:privates", // Warn if a private member is unused. 33 | "-Ypatmat-exhaust-depth", "80", // Increase max exhaustion depth 34 | ) 35 | 36 | private val scalacOptions2_12 = Seq( 37 | "-Xfuture", // Turn on future language features. 38 | "-Xlint:by-name-right-associative", // By-name parameter of right associative operator. 39 | "-Xlint:unsound-match", // Pattern match may not be typesafe. 40 | "-Ypartial-unification", // Enable partial unification in type constructor inference 41 | "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures. 42 | "-Ywarn-infer-any", // Warn when a type argument is inferred to be `Any`. 43 | "-Ywarn-nullary-override", // Warn when non-nullary `def f()" overrides nullary `def f". 44 | "-Ywarn-nullary-unit", // Warn when nullary methods return Unit. 45 | ) 46 | 47 | private val scalacOptions2_13 = Seq( 48 | ) 49 | 50 | def scalacSetting = 51 | Keys.scalacOptions := 52 | scalacOptionsCommon ++ { 53 | CrossVersion.partialVersion(Keys.scalaVersion.value) match { 54 | case Some((2, 12)) => scalacOptions2_12 55 | case Some((2, 13)) => scalacOptions2_13 56 | case _ => Seq.empty 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /interop/cats/src/main/scala/au/id/tmm/bfect/interop/cats/instanceimpls/eitherT/ConcurrentInstance.scala: -------------------------------------------------------------------------------- 1 | package au.id.tmm.bfect.interop.cats.instanceimpls.eitherT 2 | 3 | import au.id.tmm.bfect._ 4 | import ConcurrentInstance.CheckedException 5 | import au.id.tmm.bfect.effects.Concurrent 6 | import cats.data.EitherT 7 | import cats.effect.{CancelToken, Fiber} 8 | import cats.{ApplicativeError, MonadError} 9 | 10 | class ConcurrentInstance[F[_] : cats.effect.Concurrent] private[instanceimpls] 11 | extends AsyncInstance[F] 12 | with Concurrent.WithBMonad[EitherT[F, *, *]] { 13 | private def asBfectFibre[E, A](catsFiber: cats.effect.Fiber[F, Either[E, A]]): Fibre[EitherT[F, *, *], E, A] = 14 | new Fibre[EitherT[F, *, *], E, A] { 15 | override def cancel: EitherT[F, Nothing, Unit] = EitherT.liftF(catsFiber.cancel) 16 | 17 | override def join: EitherT[F, E, A] = EitherT(catsFiber.join) 18 | } 19 | 20 | override def start[E, A](fea: EitherT[F, E, A]): EitherT[F, Nothing, Fibre[EitherT[F, *, *], E, A]] = 21 | EitherT.liftF { 22 | cats.effect.Concurrent[F].map(cats.effect.Concurrent[F].start[Either[E, A]](fea.value))(asBfectFibre[E, A]) 23 | } 24 | 25 | override def racePair[E, A, B]( 26 | fea: EitherT[F, E, A], 27 | feb: EitherT[F, E, B], 28 | ): EitherT[F, E, Either[(A, Fibre[EitherT[F, *, *], E, B]), (Fibre[EitherT[F, *, *], E, A], B)]] = 29 | CheckedException 30 | .unsafeRescueCheckedExceptionsFor[F, E, Either[(A, Fiber[F, B]), (Fiber[F, A], B)]]( 31 | cats.effect 32 | .Concurrent[F] 33 | .racePair( 34 | CheckedException.raiseErrorsAsCheckedExceptionsFor(fea), 35 | CheckedException.raiseErrorsAsCheckedExceptionsFor(feb), 36 | ), 37 | ) 38 | .map { 39 | case Left((a, rightCatsFiberForB)) => 40 | Left((a, asBfectFibre(CheckedException.unsafeRescueCheckedExceptionsFor[F, E, B](rightCatsFiberForB)))) 41 | case Right((leftCatsFiberForA, b)) => 42 | Right((asBfectFibre(CheckedException.unsafeRescueCheckedExceptionsFor[F, E, A](leftCatsFiberForA)), b)) 43 | } 44 | 45 | override def cancelable[E, A]( 46 | registerForBfect: (Either[E, A] => Unit) => EitherT[F, Nothing, _], 47 | ): EitherT[F, E, A] = { 48 | val registerForCats: ((Either[Throwable, Either[E, A]] => Unit) => cats.effect.CancelToken[F]) = { 49 | cbForCats: (Either[Throwable, Either[E, A]] => Unit) => 50 | val cbForBfect: (Either[E, A] => Unit) = either => cbForCats(Right(either)) 51 | 52 | cats.effect.Concurrent[F].as(registerForBfect(cbForBfect).value, ()) 53 | } 54 | 55 | EitherT(cats.effect.Concurrent[F].cancelable(registerForCats)) 56 | } 57 | 58 | } 59 | 60 | object ConcurrentInstance { 61 | private final case class CheckedException[E](e: E) extends Exception 62 | 63 | private object CheckedException { 64 | def raiseErrorsAsCheckedExceptionsFor[F[_], E, A]( 65 | fea: EitherT[F, E, A], 66 | )(implicit 67 | monadError: MonadError[F, Throwable], 68 | ): F[A] = 69 | monadError.flatMap(fea.value) { 70 | case Left(e) => monadError.raiseError(new CheckedException(e)) 71 | case Right(a) => monadError.pure(a) 72 | } 73 | 74 | def unsafeRescueCheckedExceptionsFor[F[_], E, A]( 75 | fa: F[A], 76 | )(implicit 77 | monadError: ApplicativeError[F, Throwable], 78 | ): EitherT[F, E, A] = 79 | EitherT { 80 | monadError.recover(monadError.map[A, Either[E, A]](fa)(Right(_))) { 81 | case CheckedException(e) => Left(e.asInstanceOf[E]) 82 | } 83 | } 84 | 85 | def unsafeRescueCheckedExceptionsFor[F[_], E, A]( 86 | fiber: Fiber[F, A], 87 | )(implicit 88 | monadError: ApplicativeError[F, Throwable], 89 | ): Fiber[F, Either[E, A]] = 90 | new Fiber[F, Either[E, A]] { 91 | override def cancel: CancelToken[F] = fiber.cancel 92 | 93 | override def join: F[Either[E, A]] = unsafeRescueCheckedExceptionsFor(fiber.join).value 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/Sync.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects 17 | 18 | import scala.util.control.NonFatal 19 | 20 | trait Sync[F[_, _]] extends Die[F] { 21 | 22 | def suspend[E, A](effect: => F[E, A]): F[E, A] 23 | 24 | def failUnchecked(t: Throwable): F[Nothing, Nothing] = suspend(throw t) 25 | 26 | def sync[A](block: => A): F[Nothing, A] = suspend[Nothing, A](rightPure[Nothing, A](block)) 27 | 28 | def effectTotal[A](block: => A): F[Nothing, A] = sync(block) 29 | 30 | def syncCatch[E, A](block: => A)(catchPf: PartialFunction[Throwable, E]): F[E, A] = suspend(pureCatch(block)(catchPf)) 31 | 32 | def syncException[A](block: => A): F[Exception, A] = 33 | syncCatch(block) { 34 | case e: Exception => e 35 | } 36 | 37 | def syncThrowable[A](block: => A): F[Throwable, A] = 38 | syncCatch(block) { 39 | case NonFatal(t) => t 40 | } 41 | 42 | } 43 | 44 | object Sync extends SyncStaticOps { 45 | def apply[F[_, _] : Sync]: Sync[F] = implicitly[Sync[F]] 46 | 47 | trait ToSyncOps { 48 | implicit def toSyncOps[F[_, _], E, A](fea: F[E, A])(implicit timerInstance: Sync[F]): Ops[F, E, A] = 49 | new Ops[F, E, A](fea) 50 | 51 | implicit def toSyncOpsErrorNothing[F[_, _], A]( 52 | fea: F[Nothing, A], 53 | )(implicit 54 | timerInstance: Sync[F], 55 | ): Ops[F, Nothing, A] = 56 | new Ops[F, Nothing, A](fea) 57 | 58 | implicit def toSyncOpsValueNothing[F[_, _], E]( 59 | fea: F[E, Nothing], 60 | )(implicit 61 | timerInstance: Sync[F], 62 | ): Ops[F, E, Nothing] = 63 | new Ops[F, E, Nothing](fea) 64 | 65 | implicit def toSyncOpsErrorNothingValueNothing[F[_, _]]( 66 | fea: F[Nothing, Nothing], 67 | )(implicit 68 | timerInstance: Sync[F], 69 | ): Ops[F, Nothing, Nothing] = 70 | new Ops[F, Nothing, Nothing](fea) 71 | } 72 | 73 | final class Ops[F[_, _], E, A](fea: F[E, A])(implicit sync: Sync[F]) 74 | 75 | implicit class CloseableOps[F[_, _], E, R]( 76 | acquire: F[E, R], 77 | )(implicit 78 | sync: Sync[F], 79 | bracket: Bracket[F], 80 | e: R <:< AutoCloseable, 81 | ) { 82 | def bracketCloseable[E2 >: E, A](use: R => F[E2, A]): F[E2, A] = Sync.bracketCloseable[F, R, E, E2, A](acquire, use) 83 | } 84 | 85 | } 86 | 87 | trait SyncStaticOps extends DieStaticOps { 88 | def suspend[F[_, _] : Sync, E, A](effect: => F[E, A]): F[E, A] = Sync[F].suspend(effect) 89 | def sync[F[_, _] : Sync, A](block: => A): F[Nothing, A] = Sync[F].sync(block) 90 | def effectTotal[F[_, _] : Sync, A](block: => A): F[Nothing, A] = Sync[F].effectTotal(block) 91 | def syncCatch[F[_, _] : Sync, E, A](block: => A)(catchPf: PartialFunction[Throwable, E]): F[E, A] = 92 | Sync[F].syncCatch(block)(catchPf) 93 | def syncException[F[_, _] : Sync, A](block: => A): F[Exception, A] = Sync[F].syncException(block) 94 | def syncThrowable[F[_, _] : Sync, A](block: => A): F[Throwable, A] = Sync[F].syncThrowable(block) 95 | 96 | def bracketCloseable[F[_, _] : Sync : Bracket, R, E, E2 >: E, A]( 97 | acquire: F[E, R], 98 | use: R => F[E2, A], 99 | )(implicit 100 | e: R <:< AutoCloseable, 101 | ): F[E2, A] = 102 | Bracket[F].bracket[R, E2, A](Sync[F].leftWiden(acquire), r => Sync[F].sync(r.close()), use) 103 | 104 | } 105 | -------------------------------------------------------------------------------- /io/src/main/scala/au/id/tmm/bfect/io/IO.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io 17 | 18 | import au.id.tmm.bfect.{ExitCase, Failure} 19 | 20 | sealed trait IO[+E, +A] { 21 | 22 | def map[A2](f: A => A2): IO[E, A2] = this match { 23 | case IO.Pure(a) => IO.Pure(f(a)) 24 | case self: IO.Fail[E] => self 25 | case self: IO[E, A] => IO.FlatMap(self, (a: A) => IO.Pure(f(a))) 26 | } 27 | 28 | def leftMap[E2](f: E => E2): IO[E2, A] = 29 | foldM(f.andThen(IO.leftPure), IO.Pure(_)) 30 | 31 | def biMap[E2, A2](leftF: E => E2, rightF: A => A2): IO[E2, A2] = 32 | foldM(leftF.andThen(IO.leftPure), rightF.andThen(IO.Pure(_))) 33 | 34 | def fold[A2](leftF: E => A2, rightF: A => A2): IO[Nothing, A2] = 35 | foldM(leftF.andThen(IO.Pure(_)), rightF.andThen(IO.Pure(_))) 36 | 37 | def foldM[E2, A2](leftF: E => IO[E2, A2], rightF: A => IO[E2, A2]): IO[E2, A2] = 38 | foldCauseM( 39 | leftF = { 40 | case Failure.Checked(e) => leftF(e) 41 | case Failure.Interrupted => IO.Fail(Failure.Interrupted) 42 | case cause @ Failure.Unchecked(_) => IO.Fail(cause) 43 | }, 44 | rightF, 45 | ) 46 | 47 | def foldCauseM[E2, A2](leftF: Failure[E] => IO[E2, A2], rightF: A => IO[E2, A2]): IO[E2, A2] = 48 | IO.FoldM(this, leftF, rightF) 49 | 50 | def flatMap[E2 >: E, A2](f: A => IO[E2, A2]): IO[E2, A2] = this match { 51 | case IO.FlatMap(io, f2) => IO.FlatMap(io, f2.andThen(_.flatMap(f))) 52 | case self: IO.Fail[E] => self 53 | case self: IO[E, A] => IO.FlatMap(self, f) 54 | } 55 | 56 | def flatten[E2 >: E, A1](implicit e: A <:< IO[E2, A1]): IO[E2, A1] = flatMap(io => io) 57 | 58 | def ensure(finalizer: IO[Nothing, _]): IO[E, A] = 59 | ensureCase(_ => finalizer) 60 | 61 | def ensureCase(finalizer: ExitCase[E, Unit] => IO[Nothing, _]): IO[E, A] = 62 | this match { 63 | case IO.Ensure(io, finalizer1) => 64 | IO.Ensure(io, (exit: ExitCase[E, Unit]) => finalizer1(exit).flatMap(_ => finalizer(exit))) 65 | case self => IO.Ensure(self, finalizer) 66 | } 67 | 68 | } 69 | 70 | object IO { 71 | 72 | def pure[A](a: A): IO[Nothing, A] = Pure(a) 73 | 74 | val unit: IO[Nothing, Unit] = pure(()) 75 | 76 | def leftPure[E](e: E): IO[E, Nothing] = Fail(Failure.Checked(e)) 77 | 78 | def sync[A](block: => A): IO[Nothing, A] = Effect(() => block) 79 | 80 | def bracket[R, E, A](acquire: IO[E, R])(release: R => IO[Nothing, _])(use: R => IO[E, A]): IO[E, A] = 81 | for { 82 | resource <- acquire 83 | result <- use(resource).ensure(release(resource)) 84 | } yield result 85 | 86 | def bracketCase[R, E, A]( 87 | acquire: IO[E, R], 88 | )( 89 | release: (R, ExitCase[E, Unit]) => IO[Nothing, _], 90 | )( 91 | use: R => IO[E, A], 92 | ): IO[E, A] = 93 | for { 94 | resource <- acquire 95 | result <- use(resource).ensureCase(exitCase => release(resource, exitCase)) 96 | } yield result 97 | 98 | def racePair[E, A, B](left: IO[E, A], right: IO[E, B]): IO[E, Either[(A, IOFibre[E, B]), (IOFibre[E, A], B)]] = ??? 99 | 100 | final case class Pure[A](a: A) extends IO[Nothing, A] 101 | final case class Fail[E](cause: Failure[E]) extends IO[E, Nothing] 102 | final case class FlatMap[E, A, A2](io: IO[E, A], f: A => IO[E, A2]) extends IO[E, A2] 103 | final case class FoldM[E, E2, A, A2]( 104 | io: IO[E, A], 105 | leftF: Failure[E] => IO[E2, A2], 106 | rightF: A => IO[E2, A2], 107 | ) extends IO[E2, A2] 108 | final case class Effect[A](block: () => A) extends IO[Nothing, A] 109 | final case class Ensure[E, A](io: IO[E, A], finalizer: ExitCase[E, Unit] => IO[Nothing, _]) extends IO[E, A] 110 | 111 | } 112 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/BifunctorMonadError.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect 17 | 18 | import au.id.tmm.bfect.syntax.biFunctionK.≈> 19 | 20 | trait BifunctorMonadError[F[_, _]] extends BifunctorMonad[F] { 21 | 22 | def raiseError[E, A](e: E): F[E, A] = leftPure(e) 23 | 24 | def handleErrorWith[E1, A, E2](fea: F[E1, A])(f: E1 => F[E2, A]): F[E2, A] 25 | 26 | def recoverWith[E1, A, E2 >: E1](fea: F[E1, A])(catchPf: PartialFunction[E1, F[E2, A]]): F[E2, A] = { 27 | val totalHandler: E1 => F[E2, A] = catchPf.orElse { 28 | case e => leftPure(e) 29 | } 30 | 31 | handleErrorWith(fea)(totalHandler) 32 | } 33 | 34 | def catchLeft[E1, A, E2 >: E1](fea: F[E1, A])(catchPf: PartialFunction[E1, F[E2, A]]): F[E2, A] = 35 | recoverWith[E1, A, E2](fea)(catchPf) 36 | 37 | def onLeft[E, A](fea: F[E, A])(onPf: PartialFunction[E, F[Nothing, Unit]]): F[E, A] = 38 | handleErrorWith(fea) { e => 39 | onPf.andThen(f => flatMap(f)(_ => leftPure[E, A](e))).applyOrElse(e, (_: E) => fea) 40 | } 41 | 42 | def attempt[E, A](fea: F[E, A]): F[Nothing, Either[E, A]] = 43 | handleErrorWith[E, Either[E, A], Nothing] { 44 | rightMap(fea)(a => Right(a): Either[E, A]) 45 | } { e => 46 | rightPure(Left(e): Either[E, A]) 47 | } 48 | 49 | } 50 | 51 | object BifunctorMonadError extends BifunctorMonadErrorStaticOps { 52 | 53 | def apply[F[_, _] : BifunctorMonadError]: BifunctorMonadError[F] = implicitly[BifunctorMonadError[F]] 54 | 55 | trait ToBifunctorMonadErrorOps { 56 | implicit def toBifunctorMonadErrorOps[F[_, _], E, A](fea: F[E, A])(implicit bme: BME[F]): Ops[F, E, A] = 57 | new Ops[F, E, A](fea) 58 | 59 | implicit def toBifunctorMonadErrorOpsErrorNothing[F[_, _], A]( 60 | fea: F[Nothing, A], 61 | )(implicit 62 | bme: BME[F], 63 | ): Ops[F, Nothing, A] = 64 | new Ops[F, Nothing, A](fea) 65 | 66 | implicit def toBifunctorMonadErrorOpsValueNothing[F[_, _], E]( 67 | fea: F[E, Nothing], 68 | )(implicit 69 | bme: BME[F], 70 | ): Ops[F, E, Nothing] = 71 | new Ops[F, E, Nothing](fea) 72 | 73 | implicit def toBifunctorMonadErrorOpsErrorNothingValueNothing[F[_, _]]( 74 | fea: F[Nothing, Nothing], 75 | )(implicit 76 | bme: BME[F], 77 | ): Ops[F, Nothing, Nothing] = 78 | new Ops[F, Nothing, Nothing](fea) 79 | } 80 | 81 | final class Ops[F[_, _], E, A](fea: F[E, A])(implicit bme: BME[F]) { 82 | def attempt: F[Nothing, Either[E, A]] = bme.attempt(fea) 83 | def handleErrorWith[E2 >: E](f: E => F[E2, A]): F[E2, A] = bme.handleErrorWith[E, A, E2](fea)(f) 84 | def recoverWith[E2 >: E](catchPf: PartialFunction[E, F[E2, A]]): F[E2, A] = bme.recoverWith[E, A, E2](fea)(catchPf) 85 | def catchLeft[E2 >: E](catchPf: PartialFunction[E, F[E2, A]]): F[E2, A] = bme.catchLeft[E, A, E2](fea)(catchPf) 86 | def onLeft(onPf: PartialFunction[E, F[Nothing, Unit]]): F[E, A] = bme.onLeft(fea)(onPf) 87 | } 88 | 89 | implicit val bifunctorMonadErrorBiInvariantK: BiInvariantK[BifunctorMonadError] = 90 | new BiInvariantK[BifunctorMonadError] { 91 | override def biImapK[F[_, _], G[_, _]](F: BME[F])(fFG: F ≈> G)(fGF: G ≈> F): BME[G] = new BME[G] { 92 | override def rightPure[E, A](a: A): G[E, A] = fFG(F.rightPure(a)) 93 | 94 | override def leftPure[E, A](e: E): G[E, A] = fFG(F.leftPure(e)) 95 | 96 | override def flatMap[E1, E2 >: E1, A, B](ge1a: G[E1, A])(fage2b: A => G[E2, B]): G[E2, B] = 97 | fFG(F.flatMap[E1, E2, A, B](fGF(ge1a))(fage2b.andThen(ge2b => fGF(ge2b)))) 98 | 99 | override def tailRecM[E, A, A1](a: A)(f: A => G[E, Either[A, A1]]): G[E, A1] = 100 | fFG(F.tailRecM(a)(f.andThen { g: G[E, Either[A, A1]] => 101 | fGF(g) 102 | })) 103 | 104 | override def biMap[L1, R1, L2, R2](g: G[L1, R1])(leftF: L1 => L2, rightF: R1 => R2): G[L2, R2] = 105 | fFG(F.biMap(fGF(g))(leftF, rightF)) 106 | 107 | override def handleErrorWith[E1, A, E2](gea: G[E1, A])(f: E1 => G[E2, A]): G[E2, A] = 108 | fFG(F.handleErrorWith[E1, A, E2](fGF(gea))(f.andThen(g => fGF(g)))) 109 | } 110 | } 111 | 112 | } 113 | 114 | trait BifunctorMonadErrorStaticOps extends BifunctorMonadStaticOps { 115 | def raiseError[F[_, _] : BME, E, A](e: E): F[E, A] = BME[F].raiseError(e) 116 | } 117 | -------------------------------------------------------------------------------- /io/src/test/scala/au/id/tmm/bfect/io/EnsureRuntimeSpec.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.io 17 | 18 | import au.id.tmm.bfect.ExitCase.{Failed, Succeeded} 19 | import au.id.tmm.bfect.Failure 20 | import org.scalatest.flatspec.AnyFlatSpec 21 | 22 | class EnsureRuntimeSpec extends AnyFlatSpec { 23 | 24 | private val runtime = IORuntime() 25 | 26 | "an ensuring IO" should "ensure the execution of the finalizer after a successful IO" in { 27 | var ioHasRun = false 28 | var finalizerHasRun = false 29 | 30 | val io = IO.sync { ioHasRun = true }.ensure(IO.sync { finalizerHasRun = true }) 31 | 32 | runtime.run(io) 33 | 34 | assert(ioHasRun) 35 | assert(finalizerHasRun) 36 | } 37 | 38 | it should "ensure the execution of the finalizer after a failed IO" in { 39 | var finalizerHasRun = false 40 | 41 | val io = IO.leftPure(()).ensure(IO.sync { finalizerHasRun = true }) 42 | 43 | runtime.run(io) 44 | 45 | assert(finalizerHasRun) 46 | } 47 | 48 | it should "ensure the execution of the finalizer after an unchecked failure" in { 49 | var finalizerHasRun = false 50 | 51 | val io = IO.sync(throw new Exception).ensure(IO.sync { finalizerHasRun = true }) 52 | 53 | runtime.run(io) 54 | 55 | assert(finalizerHasRun) 56 | } 57 | 58 | "a bracketed IO" should "close the resource if the use is successful" in { 59 | var resourceAcquired = false 60 | var resourceReleased = false 61 | 62 | val io = IO.bracket( 63 | acquire = IO.sync { resourceAcquired = true }, 64 | )( 65 | release = _ => IO.sync { resourceReleased = true }, 66 | ) { _ => 67 | IO.unit 68 | } 69 | 70 | val result = runtime.run(io) 71 | 72 | assert(result === Succeeded(())) 73 | 74 | assert(resourceAcquired) 75 | assert(resourceReleased) 76 | } 77 | 78 | it should "fail if acquisition fails" in { 79 | var resourceReleased = false 80 | 81 | val io = IO.bracket( 82 | acquire = IO.leftPure(GenericError), 83 | )( 84 | release = _ => IO.sync { resourceReleased = true }, 85 | ) { _ => 86 | IO.unit 87 | } 88 | 89 | val result = runtime.run(io) 90 | 91 | assert(result === Failed(Failure.Checked(GenericError))) 92 | 93 | assert(!resourceReleased) 94 | } 95 | 96 | it should "fail if acquisition fails in an unchecked manner" in { 97 | var resourceReleased = false 98 | 99 | val exception = new Exception 100 | 101 | val io = IO.bracket( 102 | acquire = IO.sync(throw exception), 103 | )( 104 | release = _ => IO.sync { resourceReleased = true }, 105 | ) { _ => 106 | IO.unit 107 | } 108 | 109 | val result = runtime.run(io) 110 | 111 | assert(result === Failed(Failure.Unchecked(exception))) 112 | 113 | assert(!resourceReleased) 114 | } 115 | 116 | it should "close the resource if the use fails" in { 117 | var resourceAcquired = false 118 | var resourceReleased = false 119 | 120 | val io = IO.bracket( 121 | acquire = IO.sync { resourceAcquired = true }, 122 | )( 123 | release = _ => IO.sync { resourceReleased = true }, 124 | ) { _ => 125 | IO.leftPure(GenericError) 126 | } 127 | 128 | val result = runtime.run(io) 129 | 130 | assert(result === Failed(Failure.Checked(GenericError))) 131 | 132 | assert(resourceAcquired) 133 | assert(resourceReleased) 134 | } 135 | 136 | it should "close the resource if the use fails in an unchecked manner" in { 137 | var resourceAcquired = false 138 | var resourceReleased = false 139 | 140 | val exception = new Exception 141 | 142 | val io = IO.bracket( 143 | acquire = IO.sync { resourceAcquired = true }, 144 | )( 145 | release = _ => IO.sync { resourceReleased = true }, 146 | ) { _ => 147 | IO.sync(throw exception) 148 | } 149 | 150 | val result = runtime.run(io) 151 | 152 | assert(result === Failed(Failure.Unchecked(exception))) 153 | 154 | assert(resourceAcquired) 155 | assert(resourceReleased) 156 | } 157 | 158 | it should "fail if the closing of the resource fails" in { 159 | var resourceAcquired = false 160 | 161 | val exception = new Exception 162 | 163 | val io = IO.bracket( 164 | acquire = IO.sync { resourceAcquired = true }, 165 | )( 166 | release = _ => IO.sync { throw exception }, 167 | ) { _ => 168 | IO.unit 169 | } 170 | 171 | val result = runtime.run(io) 172 | 173 | assert(result === Failed(Failure.Unchecked(exception))) 174 | 175 | assert(resourceAcquired) 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/tmccarthy/bfect.svg?style=svg)](https://circleci.com/gh/tmccarthy/bfect) 2 | [![Maven Central](https://img.shields.io/maven-central/v/au.id.tmm.bfect/bfect-core_2.13.svg)](https://repo.maven.apache.org/maven2/au/id/tmm/bfect/bfect-core_2.13/) 3 | 4 | # Bfect 5 | 6 | A collection of bifunctor effect typeclasses, with instances for ZIO and conversions for cats-effect. 7 | 8 | ## Project structure 9 | 10 | * **`bfect-core`** - A collection of bifunctor effect typeclasses, based loosely around the structure of cats-effect 11 | * **`bfect-testing`** - An implementation of a bifunctor [state monad](https://typelevel.org/cats/datatypes/state.html) along with instances for the `bfect-core` typeclasses 12 | * **`bfect-interop-cats`** - Implicit conversions between the `bfect-core` typeclasses and their analogs in `cats-core` and `cats-effect` 13 | * **`bfect-interop-fs2`** - Utilities for interoperation with [`fs2`](https://github.com/functional-streams-for-scala/fs2/) 14 | * **`bfect-interop-zio`** - Instances of the `bfect-core` typeclasses for the [ZIO IO monad](https://github.com/zio/zio) 15 | * **`bfect-io`** - A half-finished bifunctor IO monad (don't use this) 16 | 17 | Each of these are available through [Maven Central](https://repo.maven.apache.org/maven2/au/id/tmm/bfect/), just add them to your project with your favourite build tool. 18 | 19 | ## Typeclasses 20 | 21 | ![](typeclass-hierarchy.svg) 22 | 23 | Typeclass | Cats equivalent | Comment | 24 | ----------|-----------------|---------| 25 | [`Bifunctor`](core/src/main/scala/au/id/tmm/bfect/Bifunctor.scala) (`BFunctor`) | `cats.Functor`/`cats.Bifunctor` | Functor with `biMap` and its derivations (`map`/`rightMap`, `leftMap`) | 26 | [`BifunctorMonad`](core/src/main/scala/au/id/tmm/bfect/BifunctorMonad.scala) (`BMonad`) | `cats.Monad` | Monad. Adds `flatMap`, `rightPure` and `leftPure`. | 27 | [`BifunctorMonadError`](core/src/main/scala/au/id/tmm/bfect/BifunctorMonadError.scala) (`BME`) | `cats.MonadError` | Represents the ability to handle errors with `handleErrorWith`. Comes with the alias `BME`. | 28 | [`effects.Bracket`](core/src/main/scala/au/id/tmm/bfect/effects/Bracket.scala) | `cats.effect.Bracket` | Bracket. Represents the pure equivalent of `try`/`finally` | 29 | [`effects.Now`](core/src/main/scala/au/id/tmm/bfect/effects/Now.scala) | `cats.effect.Timer` | Represents the ability to create a timestamp | 30 | [`effects.Timer`](core/src/main/scala/au/id/tmm/bfect/effects/Timer.scala) | `cats.effect.Timer` | Extends `Now` with the ability to delay execution for a period of time | 31 | [`effects.Die`](core/src/main/scala/au/id/tmm/bfect/effects/Die.scala) | | Extends `BifunctorMonadError` with the ability to suspend an effect that fails in an unchecked manner | 32 | [`effects.Sync`](core/src/main/scala/au/id/tmm/bfect/effects/Sync.scala) | `cats.effect.Sync` | Extends `Die` with the ability to suspend arbitrary synchronous effects | 33 | [`effects.Async`](core/src/main/scala/au/id/tmm/bfect/effects/Async.scala) | `cats.effect.Async` | Extends `Sync` with the ability to register asynchronous effects | 34 | [`effects.Concurrent`](core/src/main/scala/au/id/tmm/bfect/effects/Concurrent.scala) | `cats.effect.Concurrent` | Represents the effect of starting and cancelling tasks | 35 | [`effects.extra.Console`](core/src/main/scala/au/id/tmm/bfect/effects/extra/Console.scala) | | Represents the effect of writing to the console | 36 | [`effects.extra.EnvVars`](core/src/main/scala/au/id/tmm/bfect/effects/extra/EnvVars.scala) | | Represents the effect of accessing environment variables | 37 | [`effects.extra.Resources`](core/src/main/scala/au/id/tmm/bfect/effects/extra/Resources.scala) | | Represents the effect of accessing Java resources | 38 | [`effects.extra.Calendar`](core/src/main/scala/au/id/tmm/bfect/effects/extra/Calendar.scala) | | Extends `Now` with the ability to determine the system timezone, enabling computation of the local date and so on. | 39 | 40 | Note that unlike in cats, `Bracket` and `Concurrent` are not part of the main inheritance chain that includes `Sync` and `Async` 41 | 42 | ## Usage 43 | 44 | Use the following imports: 45 | 46 | * `import au.id.tmm.bfect.syntax.all._` for extension methods 47 | * `import au.id.tmm.bfect.instances.all._` for instances 48 | * `import au.id.tmm.bfect.implicits._` for everything 49 | 50 | ```scala 51 | import au.id.tmm.bfect.effects.Sync 52 | import au.id.tmm.bfect.implicits._ 53 | 54 | // Companion objects provide static methods: 55 | 56 | def hello1[F[_, _] : Sync]: F[Nothing, String] = Sync[F].pure("hello") 57 | def hello2[F[_, _] : Sync]: F[Nothing, String] = Sync.pure("hello") 58 | 59 | def print1[F[_, _] : Sync](string: String): F[Nothing, Unit] = Sync[F].sync(println(string)) 60 | def print2[F[_, _] : Sync](string: String): F[Nothing, Unit] = Sync.sync(println(string)) 61 | 62 | // Sync.Ops provides instance methods. The following are equivalent: 63 | 64 | def printHello1[F[_, _] : Sync]: F[Nothing, Unit] = Sync[F].flatMap(hello1)(print1) 65 | def printHello2[F[_, _] : Sync]: F[Nothing, Unit] = hello1.flatMap(print1) 66 | 67 | // Importing Sync.Ops enables for-yield syntax: 68 | 69 | def printHello3[F[_, _] : Sync]: F[Nothing, Unit] = 70 | for { 71 | hello <- hello1 72 | _ <- print1(hello) 73 | } yield () 74 | ``` 75 | 76 | ### Cats interoperation 77 | 78 | The `bfect-interop-cats` package provides implicits for interoperation with Cats. This includes instances 79 | for effect types using `EitherT`. The easiest way to access these is with the following import: 80 | 81 | ```scala 82 | import au.id.tmm.bfect.interop.cats.implicits._ 83 | ``` 84 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/Timer.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects 17 | 18 | import java.time.{Duration, Instant} 19 | 20 | import au.id.tmm.bfect.BMonad 21 | import au.id.tmm.bfect.effects.Timer.convertScalaDurationToJavaDuration 22 | 23 | import scala.concurrent.duration.{Duration => ScalaDuration, FiniteDuration => FiniteScalaDuration} 24 | 25 | trait Timer[F[_, _]] extends Now[F] { 26 | 27 | def sleep(duration: Duration): F[Nothing, Unit] 28 | 29 | def sleep(scalaDuration: ScalaDuration): F[Nothing, Unit] = 30 | sleep(convertScalaDurationToJavaDuration(scalaDuration)) 31 | 32 | def repeatFixedDelay[E, A](fea: F[E, A])(delay: Duration): F[E, Nothing] 33 | 34 | def repeatFixedDelay[E, A](fea: F[E, A], delayAsScalaDuration: ScalaDuration): F[E, Nothing] 35 | 36 | def repeatFixedRate[E, A](fea: F[E, A])(period: Duration): F[E, Nothing] 37 | 38 | def repeatFixedRate[E, A](fea: F[E, A], periodAsScalaDuration: ScalaDuration): F[E, Nothing] = 39 | repeatFixedRate(fea)(convertScalaDurationToJavaDuration(periodAsScalaDuration)) 40 | 41 | } 42 | 43 | object Timer extends TimerStaticOps { 44 | 45 | def apply[F[_, _] : Timer]: Timer[F] = implicitly[Timer[F]] 46 | 47 | trait WithBMonad[F[_, _]] extends Timer[F] { self: BMonad[F] => 48 | override def repeatFixedDelay[E, A](fea: F[E, A])(delay: Duration): F[E, Nothing] = 49 | forever(flatMap(fea)(_ => leftWiden(sleep(delay)))) 50 | 51 | override def repeatFixedDelay[E, A](fea: F[E, A], delayAsScalaDuration: ScalaDuration): F[E, Nothing] = 52 | repeatFixedDelay(fea)(convertScalaDurationToJavaDuration(delayAsScalaDuration)) 53 | 54 | override def repeatFixedRate[E, A](fea: F[E, A])(period: Duration): F[E, Nothing] = { 55 | def repeatFixedRateStartingAt(t0: Long, period: Long): F[E, Nothing] = flatMap[E, E, A, Nothing](fea) { _ => 56 | flatMap[Nothing, E, Instant, Nothing](now) { instantCompleted => 57 | val tCompleted = instantCompleted.toEpochMilli 58 | val nextStart = Instant.ofEpochMilli((period - ((tCompleted - t0) % period)) + tCompleted) 59 | val sleepDuration = Duration.between(instantCompleted, nextStart) 60 | flatMap[Nothing, E, Unit, Nothing](sleep(sleepDuration)) { _ => 61 | repeatFixedRateStartingAt(t0, period) 62 | } 63 | } 64 | } 65 | 66 | flatMap[Nothing, E, Instant, Nothing](now) { instantStarted => 67 | repeatFixedRateStartingAt(instantStarted.toEpochMilli, period.toMillis) 68 | } 69 | } 70 | } 71 | 72 | trait ToTimerOps { 73 | implicit def toTimerOps[F[_, _], E, A](fea: F[E, A])(implicit timerInstance: Timer[F]): Ops[F, E, A] = 74 | new Ops[F, E, A](fea) 75 | 76 | implicit def toTimerOpsErrorNothing[F[_, _], A]( 77 | fea: F[Nothing, A], 78 | )(implicit 79 | timerInstance: Timer[F], 80 | ): Ops[F, Nothing, A] = 81 | new Ops[F, Nothing, A](fea) 82 | 83 | implicit def toTimerOpsValueNothing[F[_, _], E]( 84 | fea: F[E, Nothing], 85 | )(implicit 86 | timerInstance: Timer[F], 87 | ): Ops[F, E, Nothing] = 88 | new Ops[F, E, Nothing](fea) 89 | 90 | implicit def toTimerOpsErrorNothingValueNothing[F[_, _]]( 91 | fea: F[Nothing, Nothing], 92 | )(implicit 93 | timerInstance: Timer[F], 94 | ): Ops[F, Nothing, Nothing] = 95 | new Ops[F, Nothing, Nothing](fea) 96 | } 97 | 98 | final class Ops[F[_, _], E, A](fea: F[E, A])(implicit timerInstance: Timer[F]) { 99 | def repeatFixedDelay(delay: Duration): F[E, Nothing] = timerInstance.repeatFixedDelay(fea)(delay) 100 | def repeatFixedDelay(delayAsScalaDuration: ScalaDuration): F[E, Nothing] = 101 | timerInstance.repeatFixedDelay(fea, delayAsScalaDuration) 102 | def repeatFixedRate(period: Duration): F[E, Nothing] = timerInstance.repeatFixedRate(fea)(period) 103 | def repeatFixedRate(periodAsScalaDuration: ScalaDuration): F[E, Nothing] = 104 | timerInstance.repeatFixedRate(fea, periodAsScalaDuration) 105 | } 106 | 107 | private[bfect] def convertScalaDurationToJavaDuration(scalaDuration: ScalaDuration): Duration = 108 | scalaDuration match { 109 | case ScalaDuration.MinusInf => Duration.ofSeconds(Long.MinValue, 0) 110 | case _: ScalaDuration.Infinite => Duration.ofSeconds(Long.MaxValue, 999999999) 111 | case finiteDuration: FiniteScalaDuration => Duration.ofNanos(finiteDuration.toNanos) 112 | } 113 | 114 | private[bfect] def convertJavaDurationToScalaDuration(javaDuration: Duration): FiniteScalaDuration = 115 | FiniteScalaDuration(javaDuration.toNanos, scala.concurrent.duration.NANOSECONDS) 116 | 117 | } 118 | 119 | trait TimerStaticOps extends NowStaticOps { 120 | def sleep[F[_, _] : Timer](duration: Duration): F[Nothing, Unit] = Timer[F].sleep(duration) 121 | def sleep[F[_, _] : Timer](scalaDuration: ScalaDuration): F[Nothing, Unit] = Timer[F].sleep(scalaDuration) 122 | } 123 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/Bifunctor.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect 17 | 18 | import au.id.tmm.bfect.syntax.biFunctionK.≈> 19 | 20 | trait Bifunctor[F[_, _]] extends BiInvariant[F] { 21 | 22 | override def biImap[L1, L2, R1, R2]( 23 | fl1r1: F[L1, R1], 24 | )( 25 | fl1l2: L1 => L2, 26 | fr1r2: R1 => R2, 27 | )( 28 | fl2l1: L2 => L1, 29 | fr2r1: R2 => R1, 30 | ): F[L2, R2] = biMap(fl1r1)(fl1l2, fr1r2) 31 | 32 | def biMap[L1, R1, L2, R2](f: F[L1, R1])(leftF: L1 => L2, rightF: R1 => R2): F[L2, R2] 33 | 34 | def rightMap[L, R1, R2](f: F[L, R1])(rightF: R1 => R2): F[L, R2] = biMap(f)(identity, rightF) 35 | 36 | def map[L, R1, R2](f: F[L, R1])(rightF: R1 => R2): F[L, R2] = biMap(f)(identity, rightF) 37 | 38 | def leftMap[L1, R, L2](f: F[L1, R])(leftF: L1 => L2): F[L2, R] = biMap(f)(leftF, identity) 39 | 40 | def mapError[L1, R, L2](f: F[L1, R])(leftF: L1 => L2): F[L2, R] = leftMap(f)(leftF) 41 | 42 | def biAs[L1, L2, R1, R2](fl1r1: F[L1, R1])(l2: L2, r2: R2): F[L2, R2] = biMap(fl1r1)(_ => l2, _ => r2) 43 | 44 | def rightAs[L, R1, R2](flr1: F[L, R1])(r2: R2): F[L, R2] = rightMap(flr1)(_ => r2) 45 | 46 | def as[L, R1, R2](flr1: F[L, R1])(r2: R2): F[L, R2] = rightAs(flr1)(r2) 47 | 48 | def unit[L, R](flr: F[L, R]): F[L, Unit] = rightAs(flr)(()) 49 | 50 | def leftAs[L1, L2, R](fl1r: F[L1, R])(l2: L2): F[L2, R] = leftMap(fl1r)(_ => l2) 51 | 52 | @inline def biWiden[L1, L2 >: L1, R1, R2 >: R1](f: F[L1, R1]): F[L2, R2] = f.asInstanceOf[F[L2, R2]] 53 | 54 | @inline def rightWiden[L, R1, R2 >: R1](f: F[L, R1]): F[L, R2] = f.asInstanceOf[F[L, R2]] 55 | 56 | @inline def widen[L, R1, R2 >: R1](f: F[L, R1]): F[L, R2] = f.asInstanceOf[F[L, R2]] 57 | 58 | @inline def leftWiden[L1, L2 >: L1, R](f: F[L1, R]): F[L2, R] = f.asInstanceOf[F[L2, R]] 59 | 60 | @inline def asExceptionFallible[A](fa: F[Nothing, A]): F[Exception, A] = leftWiden(fa) 61 | 62 | @inline def asThrowableFallible[A](fa: F[Nothing, A]): F[Throwable, A] = leftWiden(fa) 63 | 64 | } 65 | 66 | object Bifunctor { 67 | def apply[F[_, _] : Bifunctor]: Bifunctor[F] = implicitly[Bifunctor[F]] 68 | 69 | trait ToBifunctorOps { 70 | implicit def toBiFunctorOps[F[_, _], L, R](flr: F[L, R])(implicit bifunctor: Bifunctor[F]): Ops[F, L, R] = 71 | new Ops[F, L, R](flr) 72 | 73 | implicit def toBiFunctorOpsLeftNothing[F[_, _], R]( 74 | flr: F[Nothing, R], 75 | )(implicit 76 | bifunctor: Bifunctor[F], 77 | ): Ops[F, Nothing, R] = 78 | new Ops[F, Nothing, R](flr) 79 | 80 | implicit def toBiFunctorOpsRightNothing[F[_, _], L]( 81 | flr: F[L, Nothing], 82 | )(implicit 83 | bifunctor: Bifunctor[F], 84 | ): Ops[F, L, Nothing] = 85 | new Ops[F, L, Nothing](flr) 86 | 87 | implicit def toBiFunctorOpsLeftNothingRightNothing[F[_, _]]( 88 | flr: F[Nothing, Nothing], 89 | )(implicit 90 | bifunctor: Bifunctor[F], 91 | ): Ops[F, Nothing, Nothing] = 92 | new Ops[F, Nothing, Nothing](flr) 93 | } 94 | 95 | final class Ops[F[_, _], L, R](flr: F[L, R])(implicit bifunctor: Bifunctor[F]) { 96 | def biMap[L2, R2](leftF: L => L2, rightF: R => R2): F[L2, R2] = bifunctor.biMap(flr)(leftF, rightF) 97 | def rightMap[R2](rightF: R => R2): F[L, R2] = bifunctor.rightMap(flr)(rightF) 98 | def map[R2](rightF: R => R2): F[L, R2] = bifunctor.map(flr)(rightF) 99 | def leftMap[L2](leftF: L => L2): F[L2, R] = bifunctor.leftMap(flr)(leftF) 100 | def mapError[L2](leftF: L => L2): F[L2, R] = bifunctor.mapError(flr)(leftF) 101 | def biAs[L2, R2](l2: L2, r2: R2): F[L2, R2] = bifunctor.biAs(flr)(l2, r2) 102 | def rightAs[R2](r2: R2): F[L, R2] = bifunctor.rightAs(flr)(r2) 103 | def as[R2](r2: R2): F[L, R2] = bifunctor.as(flr)(r2) 104 | def unit: F[L, Unit] = bifunctor.unit(flr) 105 | def leftAs[L2](l2: L2): F[L2, R] = bifunctor.leftAs(flr)(l2) 106 | @inline def biWiden[L2 >: L, R2 >: R]: F[L2, R2] = bifunctor.biWiden(flr) 107 | @inline def rightWiden[R2 >: R]: F[L, R2] = bifunctor.rightWiden(flr) 108 | @inline def widen[R2 >: R]: F[L, R2] = bifunctor.widen(flr) 109 | @inline def leftWiden[L2 >: L]: F[L2, R] = bifunctor.leftWiden(flr) 110 | 111 | @inline def asExceptionFallible(implicit ev: L =:= Nothing): F[Exception, R] = 112 | bifunctor.asExceptionFallible(flr.asInstanceOf[F[Nothing, R]]) 113 | @inline def asThrowableFallible(implicit ev: L =:= Nothing): F[Throwable, R] = 114 | bifunctor.asThrowableFallible(flr.asInstanceOf[F[Nothing, R]]) 115 | } 116 | 117 | implicit val bifunctorBiInvariantK: BiInvariantK[Bifunctor] = new BiInvariantK[Bifunctor] { 118 | override def biImapK[F[_, _], G[_, _]](F: Bifunctor[F])(fFG: F ≈> G)(fGF: G ≈> F): Bifunctor[G] = new BFunctor[G] { 119 | override def biMap[L1, R1, L2, R2](g: G[L1, R1])(leftF: L1 => L2, rightF: R1 => R2): G[L2, R2] = 120 | fFG(F.biMap(fGF(g))(leftF, rightF)) 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/Bracket.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects 17 | 18 | import au.id.tmm.bfect.effects.Bracket.{PartialAcquire, PartialCaseAcquire} 19 | import au.id.tmm.bfect.{BMonad, ExitCase} 20 | 21 | trait Bracket[F[_, _]] { 22 | 23 | def bracketCase[R, E, A]( 24 | acquire: F[E, R], 25 | release: (R, ExitCase[E, Unit]) => F[Nothing, _], 26 | use: R => F[E, A], 27 | ): F[E, A] 28 | 29 | /** 30 | * Returns a curried form of `bracketCase` which has better type inference. 31 | */ 32 | def bracketCase[R, E](acquire: F[E, R]): PartialCaseAcquire[F, R, E] = Bracket.bracketCase(acquire) 33 | 34 | def bracket[R, E, A]( 35 | acquire: F[E, R], 36 | release: R => F[Nothing, _], 37 | use: R => F[E, A], 38 | ): F[E, A] = 39 | bracketCase[R, E, A](acquire, { case (resource, exitCase) => release(resource) }, use) 40 | 41 | /** 42 | * Returns a curried form of `bracket` which has better type inference 43 | */ 44 | def bracket[R, E](acquire: F[E, R]): PartialAcquire[F, R, E] = Bracket.bracket(acquire) 45 | 46 | def ensure[E, A](fea: F[E, A])(finalizer: F[Nothing, _]): F[E, A] = 47 | ensureCase[E, A](fea)(_ => finalizer) 48 | 49 | def ensureCase[E, A](fea: F[E, A])(finalizer: ExitCase[E, Unit] => F[Nothing, _]): F[E, A] 50 | 51 | } 52 | 53 | object Bracket extends BracketStaticOps { 54 | def apply[F[_, _] : Bracket]: Bracket[F] = implicitly[Bracket[F]] 55 | 56 | trait WithBMonad[F[_, _]] extends Bracket[F] { self: BMonad[F] => 57 | override def ensure[E, A](fea: F[E, A])(finalizer: F[Nothing, _]): F[E, A] = 58 | bracket[Unit, E, A](rightPure(()), _ => finalizer, _ => fea) 59 | 60 | override def ensureCase[E, A](fea: F[E, A])(finalizer: ExitCase[E, Unit] => F[Nothing, _]): F[E, A] = 61 | bracketCase[Unit, E, A](rightPure(()), { case (resource, exitCase) => finalizer(exitCase) }, _ => fea) 62 | } 63 | 64 | trait ToBracketOps { 65 | implicit def toBracketOps[F[_, _], E, A](fea: F[E, A])(implicit timerInstance: Bracket[F]): Ops[F, E, A] = 66 | new Ops[F, E, A](fea) 67 | 68 | implicit def toBracketOpsErrorNothing[F[_, _], A]( 69 | fea: F[Nothing, A], 70 | )(implicit 71 | timerInstance: Bracket[F], 72 | ): Ops[F, Nothing, A] = 73 | new Ops[F, Nothing, A](fea) 74 | 75 | implicit def toBracketOpsValueNothing[F[_, _], E]( 76 | fea: F[E, Nothing], 77 | )(implicit 78 | timerInstance: Bracket[F], 79 | ): Ops[F, E, Nothing] = 80 | new Ops[F, E, Nothing](fea) 81 | 82 | implicit def toBracketOpsErrorNothingValueNothing[F[_, _]]( 83 | fea: F[Nothing, Nothing], 84 | )(implicit 85 | timerInstance: Bracket[F], 86 | ): Ops[F, Nothing, Nothing] = 87 | new Ops[F, Nothing, Nothing](fea) 88 | } 89 | 90 | final class Ops[F[_, _], E, A](fea: F[E, A])(implicit bracket: Bracket[F]) { 91 | def ensure(finalizer: F[Nothing, _]): F[E, A] = bracket.ensure(fea)(finalizer) 92 | def ensureCase(finalizer: ExitCase[E, Unit] => F[Nothing, _]): F[E, A] = bracket.ensureCase(fea)(finalizer) 93 | } 94 | 95 | final class PartialCaseAcquire[F[_, _], R, E] private[effects] (acquire: F[E, R]) { 96 | def apply[A](release: (R, ExitCase[E, Unit]) => F[Nothing, _]): PartialCaseAcquireRelease[F, R, E, A] = 97 | new PartialCaseAcquireRelease[F, R, E, A](acquire, release) 98 | } 99 | 100 | final class PartialCaseAcquireRelease[F[_, _], R, E, A] private[effects] ( 101 | acquire: F[E, R], 102 | release: (R, ExitCase[E, Unit]) => F[Nothing, _], 103 | ) { 104 | def apply(use: R => F[E, A])(bracket: Bracket[F]): F[E, A] = bracket.bracketCase(acquire, release, use) 105 | } 106 | 107 | final class PartialAcquire[F[_, _], R, E] private[effects] (acquire: F[E, R]) { 108 | def apply(release: R => F[Nothing, _]): PartialAcquireRelease[F, R, E] = 109 | new PartialAcquireRelease[F, R, E](acquire, release) 110 | } 111 | 112 | final class PartialAcquireRelease[F[_, _], R, E] private[effects] (acquire: F[E, R], release: R => F[Nothing, _]) { 113 | def apply[A](use: R => F[E, A])(implicit bracket: Bracket[F]): F[E, A] = 114 | bracket.bracket[R, E, A](acquire, release, use) 115 | } 116 | 117 | } 118 | 119 | trait BracketStaticOps { 120 | 121 | def bracketCase[F[_, _] : Bracket, R, E, A]( 122 | acquire: F[E, R], 123 | release: (R, ExitCase[E, Unit]) => F[Nothing, _], 124 | use: R => F[E, A], 125 | ): F[E, A] = Bracket[F].bracketCase[R, E, A](acquire, release, use) 126 | 127 | def bracket[F[_, _] : Bracket, R, E, A]( 128 | acquire: F[E, R], 129 | release: R => F[Nothing, _], 130 | use: R => F[E, A], 131 | ): F[E, A] = Bracket[F].bracket[R, E, A](acquire, release, use) 132 | 133 | /** 134 | * Returns a curried form of `bracketCase` which has better type inference. 135 | */ 136 | def bracketCase[F[_, _], R, E](acquire: F[E, R]): PartialCaseAcquire[F, R, E] = 137 | new PartialCaseAcquire[F, R, E](acquire) 138 | 139 | /** 140 | * Returns a curried form of `bracket` which has better type inference 141 | */ 142 | def bracket[F[_, _], R, E](acquire: F[E, R]): PartialAcquire[F, R, E] = 143 | new PartialAcquire[F, R, E](acquire) 144 | 145 | } 146 | -------------------------------------------------------------------------------- /testing/src/test/scala/au/id/tmm/bfect/effects/TimerSpec.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects 17 | 18 | import java.time.{Duration, Instant} 19 | 20 | import au.id.tmm.bfect.syntax.timer._ 21 | import au.id.tmm.bfect.effects.TimerSpec.{TimerTestIO, TimerTestState, dummyTask} 22 | import au.id.tmm.bfect.testing.BState 23 | import org.scalatest.flatspec.AnyFlatSpec 24 | 25 | import scala.concurrent.duration.{Duration => ScalaDuration} 26 | 27 | // Ideally this test would be in the tests for bfect-core, but sbt can't handle the cyclic dependency on BState 28 | class TimerSpec extends AnyFlatSpec { 29 | 30 | // Need to bring this into scope to override the default implementation provided by the `Concurrent` instance 31 | private implicit val timerInstance: Timer[TimerTestIO] = TimerSpec.timerInstanceForTests 32 | 33 | "repeating with a fixed delay" should "repeat with the same delay length if the task takes less than the duration" in { 34 | val task = dummyTask(Duration.ofSeconds(1), failsAfter = Instant.EPOCH.plusSeconds(10)) 35 | 36 | val stateAfterExecution = task 37 | .repeatFixedDelay(Duration.ofSeconds(5)) 38 | .runS(TimerTestState(Instant.EPOCH)) 39 | 40 | val expectedTaskExecutionTimes = List( 41 | Instant.EPOCH, 42 | Instant.EPOCH.plusSeconds(6), 43 | Instant.EPOCH.plusSeconds(12), 44 | ) 45 | 46 | assert(stateAfterExecution.taskExecutionTimes === expectedTaskExecutionTimes) 47 | } 48 | 49 | it should "repeat with the same delay even if the task takes more than the duration" in { 50 | val task = dummyTask(Duration.ofSeconds(6), failsAfter = Instant.EPOCH.plusSeconds(20)) 51 | 52 | val stateAfterExecution = task 53 | .repeatFixedDelay(Duration.ofSeconds(5)) 54 | .runS(TimerTestState(Instant.EPOCH)) 55 | 56 | val expectedTaskExecutionTimes = List( 57 | Instant.EPOCH, 58 | Instant.EPOCH.plusSeconds(11), 59 | Instant.EPOCH.plusSeconds(22), 60 | ) 61 | 62 | assert(stateAfterExecution.taskExecutionTimes === expectedTaskExecutionTimes) 63 | } 64 | 65 | "repeating with a fixed rate" should "repeat with the same period if the task takes less than the period" in { 66 | val task = dummyTask(Duration.ofSeconds(1), failsAfter = Instant.EPOCH.plusSeconds(10)) 67 | 68 | val stateAfterExecution = task 69 | .repeatFixedRate(Duration.ofSeconds(5)) 70 | .runS(TimerTestState(Instant.EPOCH)) 71 | 72 | val expectedTaskExecutionTimes = List( 73 | Instant.EPOCH, 74 | Instant.EPOCH.plusSeconds(5), 75 | Instant.EPOCH.plusSeconds(10), 76 | Instant.EPOCH.plusSeconds(15), 77 | ) 78 | 79 | assert(stateAfterExecution.taskExecutionTimes === expectedTaskExecutionTimes) 80 | } 81 | 82 | it should "repeat with the same period if the initial time is nonzero" in { 83 | val t0 = Instant.EPOCH.plusSeconds(42) 84 | 85 | val task = dummyTask(Duration.ofSeconds(1), failsAfter = t0.plusSeconds(10)) 86 | 87 | val stateAfterExecution = task 88 | .repeatFixedRate(Duration.ofSeconds(5)) 89 | .runS(TimerTestState(t0)) 90 | 91 | val expectedTaskExecutionTimes = List( 92 | t0, 93 | t0.plusSeconds(5), 94 | t0.plusSeconds(10), 95 | t0.plusSeconds(15), 96 | ) 97 | 98 | assert(stateAfterExecution.taskExecutionTimes === expectedTaskExecutionTimes) 99 | } 100 | 101 | it should "skip executions if the task takes more than the period" in { 102 | val task = dummyTask(Duration.ofSeconds(6), failsAfter = Instant.EPOCH.plusSeconds(20)) 103 | 104 | val stateAfterExecution = task 105 | .repeatFixedRate(Duration.ofSeconds(5)) 106 | .runS(TimerTestState(Instant.EPOCH)) 107 | 108 | val expectedTaskExecutionTimes = List( 109 | Instant.EPOCH, 110 | Instant.EPOCH.plusSeconds(10), 111 | Instant.EPOCH.plusSeconds(20), 112 | Instant.EPOCH.plusSeconds(30), 113 | ) 114 | 115 | assert(stateAfterExecution.taskExecutionTimes === expectedTaskExecutionTimes) 116 | } 117 | 118 | "the conversion of scala durations to java durations" should "convert an infinite scala duration to the maximum java duration" in { 119 | val javaDuration = Timer.convertScalaDurationToJavaDuration(ScalaDuration.Inf) 120 | 121 | assert(javaDuration.getSeconds === Long.MaxValue) 122 | assert(javaDuration.getNano === 999999999) 123 | } 124 | 125 | it should "convert an undefined scala duration to the maximum java duration" in { 126 | val javaDuration = Timer.convertScalaDurationToJavaDuration(ScalaDuration.Undefined) 127 | 128 | assert(javaDuration.getSeconds === Long.MaxValue) 129 | assert(javaDuration.getNano === 999999999) 130 | } 131 | 132 | it should "convert a negative infinite scala duration to the minimum java duration" in { 133 | val javaDuration = Timer.convertScalaDurationToJavaDuration(ScalaDuration.MinusInf) 134 | 135 | assert(javaDuration.getSeconds === Long.MinValue) 136 | assert(javaDuration.getNano === 0) 137 | } 138 | 139 | it should "convert a zero scala duration to the zero java duration" in { 140 | val javaDuration = Timer.convertScalaDurationToJavaDuration(ScalaDuration.Zero) 141 | 142 | assert(javaDuration.getSeconds === 0) 143 | assert(javaDuration.getNano === 0) 144 | } 145 | 146 | } 147 | 148 | object TimerSpec { 149 | 150 | private final case class TimerTestState(now: Instant, taskExecutionTimes: List[Instant] = List.empty) { 151 | def proceedBy(duration: Duration): TimerTestState = TimerTestState(now.plus(duration), taskExecutionTimes) 152 | def executeTask: TimerTestState = TimerTestState(now, taskExecutionTimes = taskExecutionTimes :+ now) 153 | } 154 | 155 | private type TimerTestIO[+E, +A] = BState[TimerTestState, E, A] 156 | 157 | private implicit val timerInstanceForTests: Timer[TimerTestIO] = new BState.TimerInstance[TimerTestState] { 158 | override def nowFromState(state: TimerTestState): (TimerTestState, Instant) = (state, state.now) 159 | 160 | override def applySleepToState(sleepDuration: Duration, state: TimerTestState): TimerTestState = 161 | state.proceedBy(sleepDuration) 162 | } 163 | 164 | private def dummyTask(duration: Duration, failsAfter: Instant): TimerTestIO[Unit, Unit] = 165 | BState { oldState => 166 | if (oldState.now.isAfter(failsAfter)) { 167 | (oldState.executeTask, Left(())) 168 | } else { 169 | (oldState.executeTask.proceedBy(duration), Right(())) 170 | } 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/BifunctorMonad.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect 17 | 18 | import au.id.tmm.bfect.syntax.biFunctionK.≈> 19 | 20 | import scala.util.Try 21 | 22 | trait BifunctorMonad[F[_, _]] extends Bifunctor[F] { 23 | 24 | def rightPure[E, A](a: A): F[E, A] 25 | 26 | def pure[E, A](a: A): F[E, A] = rightPure(a) 27 | 28 | def unit[E]: F[E, Unit] = rightPure(()) 29 | 30 | def leftPure[E, A](e: E): F[E, A] 31 | 32 | def fromOption[E, A](option: Option[A], ifNone: => E): F[E, A] = option match { 33 | case Some(a) => rightPure(a) 34 | case None => leftPure(ifNone) 35 | } 36 | 37 | def fromEither[E, A](either: Either[E, A]): F[E, A] = either match { 38 | case Left(e) => leftPure(e) 39 | case Right(a) => rightPure(a) 40 | } 41 | 42 | def fromTry[A](aTry: Try[A]): F[Throwable, A] = aTry match { 43 | case scala.util.Success(a) => rightPure(a) 44 | case scala.util.Failure(e) => leftPure(e) 45 | } 46 | 47 | def pureCatch[E, A](block: => A)(catchPf: PartialFunction[Throwable, E]): F[E, A] = 48 | try { 49 | rightPure(block): F[E, A] 50 | } catch { 51 | catchPf.andThen(leftPure(_): F[E, A]) 52 | } 53 | 54 | def pureCatchException[A](block: => A): F[Exception, A] = pureCatch(block) { 55 | case e: Exception => e 56 | } 57 | 58 | def pureCatchThrowable[A](block: => A): F[Throwable, A] = pureCatch(block) { 59 | case t: Throwable => t 60 | } 61 | 62 | def flatten[E1, E2 >: E1, A](fefa: F[E1, F[E2, A]]): F[E2, A] = flatMap[E1, E2, F[E2, A], A](fefa)(identity) 63 | 64 | def flatMap[E1, E2 >: E1, A, B](fe1a: F[E1, A])(fafe2b: A => F[E2, B]): F[E2, B] 65 | 66 | def forever[E, A](fea: F[E, A]): F[E, Nothing] = flatMap[E, E, A, Nothing](fea) { a => 67 | tailRecM[E, A, Nothing](a) { _ => 68 | map(fea)(Left.apply) 69 | } 70 | } 71 | 72 | /** 73 | * Keeps calling `f` until a `scala.util.Right[B]` is returned. 74 | */ 75 | def tailRecM[E, A, A1](a: A)(f: A => F[E, Either[A, A1]]): F[E, A1] 76 | 77 | def absolve[E, E2 >: E, A](fEitherEA: F[E, Either[E2, A]]): F[E2, A] = 78 | flatMap[E, E2, Either[E2, A], A](fEitherEA)(fromEither) 79 | 80 | def absolveOption[E, A](feOptionA: F[E, Option[A]], ifNone: => E): F[E, A] = flatMap(feOptionA)(fromOption(_, ifNone)) 81 | 82 | } 83 | 84 | object BifunctorMonad extends BifunctorMonadStaticOps { 85 | def apply[F[_, _] : BifunctorMonad]: BifunctorMonad[F] = implicitly[BifunctorMonad[F]] 86 | 87 | trait ToBifunctorMonadOps { 88 | implicit def toBifunctorMonadOps[F[_, _], E, A]( 89 | fea: F[E, A], 90 | )(implicit 91 | bifunctorMonad: BifunctorMonad[F], 92 | ): Ops[F, E, A] = 93 | new Ops(fea) 94 | 95 | implicit def toBifunctorMonadOpsErrorNothing[F[_, _], A]( 96 | fea: F[Nothing, A], 97 | )(implicit 98 | bifunctorMonad: BifunctorMonad[F], 99 | ): Ops[F, Nothing, A] = 100 | new Ops[F, Nothing, A](fea) 101 | 102 | implicit def toBifunctorMonadOpsValueNothing[F[_, _], E]( 103 | fea: F[E, Nothing], 104 | )(implicit 105 | bifunctorMonad: BifunctorMonad[F], 106 | ): Ops[F, E, Nothing] = 107 | new Ops[F, E, Nothing](fea) 108 | 109 | implicit def toBifunctorMonadOpsErrorNothingValueNothing[F[_, _]]( 110 | fea: F[Nothing, Nothing], 111 | )(implicit 112 | bifunctorMonad: BifunctorMonad[F], 113 | ): Ops[F, Nothing, Nothing] = 114 | new Ops[F, Nothing, Nothing](fea) 115 | 116 | implicit def toBifunctorMonadFlattenOps[F[_, _], E1, E2 >: E1, A]( 117 | fefea: F[E1, F[E2, A]], 118 | )(implicit 119 | bifunctorMonad: BifunctorMonad[F], 120 | ): FlattenOps[F, E1, E2, A] = 121 | new FlattenOps[F, E1, E2, A](fefea) 122 | 123 | implicit def toBifunctorMonadAbsolveOps[F[_, _], E, E2 >: E, A]( 124 | fEitherEA: F[E, Either[E2, A]], 125 | )(implicit 126 | bMonad: BMonad[F], 127 | ): AbsolveOps[F, E, E2, A] = 128 | new AbsolveOps[F, E, E2, A](fEitherEA) 129 | 130 | implicit def toBifunctorMonadAbsolveNothingErrorOps[F[_, _], E2, A]( 131 | fEitherEA: F[Nothing, Either[E2, A]], 132 | )(implicit 133 | bMonad: BMonad[F], 134 | ): AbsolveOps[F, Nothing, E2, A] = 135 | new AbsolveOps[F, Nothing, E2, A](fEitherEA) 136 | 137 | implicit def toBifunctorMonadAbsolveNothingValueOps[F[_, _], E, E2 >: E]( 138 | fEitherEA: F[E, Either[E2, Nothing]], 139 | )(implicit 140 | bMonad: BMonad[F], 141 | ): AbsolveOps[F, E, E2, Nothing] = 142 | new AbsolveOps[F, E, E2, Nothing](fEitherEA) 143 | 144 | implicit def toBifunctorMonadAbsolveOptionOps[F[_, _], E, A]( 145 | feOptionA: F[E, Option[A]], 146 | )(implicit 147 | bMonad: BMonad[F], 148 | ): AbsolveOptionOps[F, E, A] = 149 | new AbsolveOptionOps[F, E, A](feOptionA) 150 | 151 | implicit def toBifunctorMonadAbsolveOptionNothingErrorOps[F[_, _], A]( 152 | feOptionA: F[Nothing, Option[A]], 153 | )(implicit 154 | bMonad: BMonad[F], 155 | ): AbsolveOptionOps[F, Nothing, A] = 156 | new AbsolveOptionOps[F, Nothing, A](feOptionA) 157 | } 158 | 159 | final class Ops[F[_, _], E, A](fea: F[E, A])(implicit bifunctorMonad: BifunctorMonad[F]) { 160 | def flatMap[E2 >: E, B](f: A => F[E2, B]): F[E2, B] = bifunctorMonad.flatMap[E, E2, A, B](fea)(f) 161 | def >>[E2 >: E, B](f: A => F[E2, B]): F[E2, B] = bifunctorMonad.flatMap[E, E2, A, B](fea)(f) 162 | def forever: F[E, Nothing] = bifunctorMonad.forever(fea) 163 | } 164 | 165 | final class FlattenOps[F[_, _], E1, E2 >: E1, A]( 166 | fefea: F[E1, F[E2, A]], 167 | )(implicit 168 | bifunctorMonad: BifunctorMonad[F], 169 | ) { 170 | def flatten: F[E2, A] = bifunctorMonad.flatten[E1, E2, A](fefea) 171 | } 172 | 173 | final class AbsolveOps[F[_, _], E, E2 >: E, A](fEitherEA: F[E, Either[E2, A]])(implicit bMonad: BMonad[F]) { 174 | def absolve: F[E2, A] = bMonad.absolve(fEitherEA) 175 | } 176 | 177 | final class AbsolveOptionOps[F[_, _], E, A](feOptionA: F[E, Option[A]])(implicit bMonad: BMonad[F]) { 178 | def absolveOption[E2 >: E](ifNone: => E2): F[E2, A] = 179 | bMonad.absolveOption[E2, A](bMonad.leftWiden(feOptionA), ifNone) 180 | } 181 | 182 | implicit val bifunctorMonadBiInvariantK: BiInvariantK[BifunctorMonad] = new BiInvariantK[BMonad] { 183 | override def biImapK[F[_, _], G[_, _]](F: BMonad[F])(fFG: F ≈> G)(fGF: G ≈> F): BMonad[G] = new BMonad[G] { 184 | override def rightPure[E, A](a: A): G[E, A] = fFG(F.rightPure(a)) 185 | 186 | override def leftPure[E, A](e: E): G[E, A] = fFG(F.leftPure(e)) 187 | 188 | override def flatMap[E1, E2 >: E1, A, B](ge1a: G[E1, A])(fage2b: A => G[E2, B]): G[E2, B] = 189 | fFG(F.flatMap[E1, E2, A, B](fGF(ge1a))(fage2b.andThen(ge2b => fGF(ge2b)))) 190 | 191 | override def tailRecM[E, A, A1](a: A)(f: A => G[E, Either[A, A1]]): G[E, A1] = 192 | fFG(F.tailRecM(a)(f.andThen { g: G[E, Either[A, A1]] => 193 | fGF(g) 194 | })) 195 | 196 | override def biMap[L1, R1, L2, R2](g: G[L1, R1])(leftF: L1 => L2, rightF: R1 => R2): G[L2, R2] = 197 | fFG(F.biMap(fGF(g))(leftF, rightF)) 198 | } 199 | } 200 | 201 | } 202 | 203 | trait BifunctorMonadStaticOps { 204 | def rightPure[F[_, _] : BifunctorMonad, E, A](a: A): F[E, A] = BifunctorMonad[F].rightPure(a) 205 | def pure[F[_, _] : BifunctorMonad, E, A](a: A): F[E, A] = BifunctorMonad[F].pure(a) 206 | def leftPure[F[_, _] : BifunctorMonad, E, A](e: E): F[E, A] = BifunctorMonad[F].leftPure(e) 207 | def fromEither[F[_, _] : BifunctorMonad, E, A](either: Either[E, A]): F[E, A] = BifunctorMonad[F].fromEither(either) 208 | def fromOption[F[_, _] : BifunctorMonad, E, A](option: Option[A], ifNone: => E): F[E, A] = 209 | BifunctorMonad[F].fromOption(option, ifNone) 210 | def fromTry[F[_, _] : BifunctorMonad, A](aTry: Try[A]): F[Throwable, A] = BifunctorMonad[F].fromTry(aTry) 211 | def pureCatch[F[_, _] : BifunctorMonad, E, A](block: => A)(catchPf: PartialFunction[Throwable, E]): F[E, A] = 212 | BifunctorMonad[F].pureCatch(block)(catchPf) 213 | def pureCatchException[F[_, _] : BifunctorMonad, A](block: => A): F[Exception, A] = 214 | BifunctorMonad[F].pureCatchException(block) 215 | def pureCatchThrowable[F[_, _] : BifunctorMonad, A](block: => A): F[Throwable, A] = 216 | BifunctorMonad[F].pureCatchThrowable(block) 217 | 218 | def unit[F[_, _] : BifunctorMonad]: F[Nothing, Unit] = BifunctorMonad[F].unit 219 | } 220 | -------------------------------------------------------------------------------- /interop/zio/src/main/scala/au/id/tmm/bfect/interop/zio/ZioInstances.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.interop.zio 17 | 18 | import java.time._ 19 | import java.util.concurrent.TimeUnit 20 | 21 | import au.id.tmm.bfect._ 22 | import au.id.tmm.bfect.effects._ 23 | import au.id.tmm.bfect.effects.extra.{Calendar, Console, EnvVars, Resources} 24 | import zio.clock.Clock 25 | import zio.duration.{Duration => ZioDuration} 26 | import zio.{Exit, Fiber, IO} 27 | 28 | import scala.concurrent.duration.{Duration => ScalaDuration} 29 | 30 | object ZioInstanceMixins { 31 | 32 | trait ZioBifunctor extends Bifunctor[IO] { 33 | override def biMap[L1, R1, L2, R2](f: IO[L1, R1])(leftF: L1 => L2, rightF: R1 => R2): IO[L2, R2] = 34 | f.bimap(leftF, rightF) 35 | override def rightMap[L, R1, R2](f: IO[L, R1])(rightF: R1 => R2): IO[L, R2] = f.map(rightF) 36 | override def leftMap[L1, R, L2](f: IO[L1, R])(leftF: L1 => L2): IO[L2, R] = f.mapError(leftF) 37 | } 38 | 39 | trait ZioBMonad extends BifunctorMonad[IO] { self: BFunctor[IO] => 40 | override def rightPure[E, A](a: A): IO[E, A] = IO.succeed(a) 41 | override def leftPure[E, A](e: E): IO[E, A] = IO.fail(e) 42 | override def flatMap[E1, E2 >: E1, A, B](fe1a: IO[E1, A])(fafe2b: A => IO[E2, B]): IO[E2, B] = fe1a.flatMap(fafe2b) 43 | override def tailRecM[E, A, A1](a: A)(f: A => IO[E, Either[A, A1]]): IO[E, A1] = 44 | f(a).flatMap { 45 | case Left(l) => tailRecM(l)(f) 46 | case Right(r) => IO.succeed(r) 47 | } 48 | } 49 | 50 | trait ZioBME extends BME[IO] { self: BMonad[IO] => 51 | override def handleErrorWith[E1, A, E2](fea: IO[E1, A])(f: E1 => IO[E2, A]): IO[E2, A] = fea.catchAll(f) 52 | override def recoverWith[E1, A, E2 >: E1](fea: IO[E1, A])(catchPf: PartialFunction[E1, IO[E2, A]]): IO[E2, A] = 53 | fea.catchSome(catchPf) 54 | override def attempt[E, A](fea: IO[E, A]): IO[Nothing, Either[E, A]] = fea.either 55 | } 56 | 57 | trait ZioBracket extends Bracket.WithBMonad[IO] { self: BME[IO] => 58 | 59 | private def bfectExitCaseFrom[E, A](zioExit: zio.Exit[E, A]): ExitCase[E, A] = zioExit match { 60 | case Exit.Success(value) => ExitCase.Succeeded(value) 61 | case Exit.Failure(cause) => ExitCase.Failed(DataConversions.zioCauseToBfectFailure(cause)) 62 | } 63 | 64 | override def bracketCase[R, E, A]( 65 | acquire: IO[E, R], 66 | release: (R, ExitCase[E, Unit]) => IO[Nothing, _], 67 | use: R => IO[E, A], 68 | ): IO[E, A] = { 69 | val releaseForZioBracket: (R, zio.Exit[E, A]) => IO[Nothing, _] = { 70 | case (resource, zioExit) => 71 | release(resource, bfectExitCaseFrom(zioExit).as(())) 72 | } 73 | 74 | IO.bracketExit[E, R, A](acquire, releaseForZioBracket, use) 75 | } 76 | 77 | override def bracket[R, E, A]( 78 | acquire: IO[E, R], 79 | release: R => IO[Nothing, _], 80 | use: R => IO[E, A], 81 | ): IO[E, A] = 82 | IO.bracket[E, R, A](acquire, release, use) 83 | 84 | override def ensure[E, A](fea: IO[E, A])(finalizer: IO[Nothing, _]): IO[E, A] = 85 | fea.ensuring(finalizer) 86 | } 87 | 88 | trait ZioDie extends Die[IO] { self: BME[IO] => 89 | override def failUnchecked(t: Throwable): IO[Nothing, Nothing] = IO.die(t) 90 | 91 | override def orDie[E, A](fea: IO[E, A])(implicit ev: E <:< Throwable): IO[Nothing, A] = fea.orDie 92 | 93 | override def refineOrDie[E1, A, E2]( 94 | fea: IO[E1, A], 95 | )( 96 | refinePf: PartialFunction[E1, E2], 97 | )(implicit 98 | ev: E1 <:< Throwable, 99 | ): IO[E2, A] = fea.refineOrDie(refinePf) 100 | } 101 | 102 | trait ZioSync extends Sync[IO] with ZioDie { self: BME[IO] => 103 | 104 | override def suspend[E, A](effect: => IO[E, A]): IO[E, A] = IO.effectSuspendTotal(effect) 105 | 106 | override def sync[A](block: => A): IO[Nothing, A] = 107 | IO.effectTotal(block) 108 | 109 | //noinspection ConvertibleToMethodValue 110 | override def syncCatch[E, A](block: => A)(catchPf: PartialFunction[Throwable, E]): IO[E, A] = { 111 | val catchTotal: Throwable => IO[E, A] = t => 112 | catchPf.andThen(IO.fail(_)).applyOrElse(t, (t: Throwable) => IO.die(t)) 113 | 114 | IO.effect(block).catchAll(catchTotal) 115 | } 116 | 117 | override def syncThrowable[A](block: => A): IO[Throwable, A] = IO.effect(block) 118 | 119 | override def failUnchecked(t: Throwable): IO[Nothing, Nothing] = super.failUnchecked(t) 120 | } 121 | 122 | trait ZioAsync extends Async[IO] { self: Sync[IO] => 123 | override def async[E, A](registerForTmm: (Either[E, A] => Unit) => Unit): IO[E, A] = { 124 | val registerForZio: (IO[E, A] => Unit) => Unit = { cbForZio => 125 | val cbForTmm: Either[E, A] => Unit = either => cbForZio(IO.fromEither(either)) 126 | 127 | registerForTmm(cbForTmm) 128 | } 129 | 130 | IO.effectAsync(registerForZio) 131 | } 132 | 133 | override def asyncF[E, A](registerForTmm: (Either[E, A] => Unit) => IO[Nothing, _]): IO[E, A] = { 134 | val registerForZio: (IO[E, A] => Unit) => IO[Nothing, _] = { cbForZio => 135 | val cbForTmm: Either[E, A] => Unit = either => cbForZio(IO.fromEither(either)) 136 | 137 | registerForTmm(cbForTmm) 138 | } 139 | 140 | IO.effectAsyncM(registerForZio) 141 | } 142 | } 143 | 144 | trait ZioTimer extends Timer.WithBMonad[IO] { self: BME[IO] => 145 | private val clock = Clock.Service.live 146 | 147 | override def sleep(duration: Duration): IO[Nothing, Unit] = clock.sleep(ZioDuration.fromJava(duration)) 148 | override def sleep(scalaDuration: ScalaDuration): IO[Nothing, Unit] = 149 | clock.sleep(ZioDuration.fromScala(scalaDuration)) 150 | override def now: IO[Nothing, Instant] = 151 | clock.currentTime(TimeUnit.NANOSECONDS).map(nanos => Instant.ofEpochSecond(nanos / 1000000000, nanos)) 152 | } 153 | 154 | trait ZioConcurrent extends Concurrent[IO] { self: BMonad[IO] => 155 | def bfectFibreFrom[E, A](zioFiber: Fiber[E, A]): Fibre[IO, E, A] = new Fibre[IO, E, A] { 156 | override def cancel: IO[Nothing, Unit] = zioFiber.interrupt.unit 157 | override def join: IO[E, A] = zioFiber.join 158 | } 159 | 160 | override def start[E, A](fea: IO[E, A]): IO[Nothing, Fibre[IO, E, A]] = fea.fork.map(bfectFibreFrom) 161 | 162 | override def racePair[E, A, B]( 163 | left: IO[E, A], 164 | right: IO[E, B], 165 | ): IO[E, Either[(A, Fibre[IO, E, B]), (Fibre[IO, E, A], B)]] = 166 | left.raceWith(right)( 167 | leftDone = { 168 | case (Exit.Failure(cause), rightFiber) => rightFiber.interrupt.zipRight(IO.halt(cause)) 169 | case (Exit.Success(leftValue), rightFiber) => rightPure(Left((leftValue, bfectFibreFrom(rightFiber)))) 170 | }, 171 | rightDone = { 172 | case (Exit.Failure(cause), leftFiber) => leftFiber.interrupt.zipRight(IO.halt(cause)) 173 | case (Exit.Success(rightValue), leftFiber) => rightPure(Right((bfectFibreFrom(leftFiber), rightValue))) 174 | }, 175 | ) 176 | 177 | override def race[E, A, B](fea: IO[E, A], feb: IO[E, B]): IO[E, Either[A, B]] = fea.raceEither(feb) 178 | 179 | override def par[E, A, B](fea: IO[E, A], feb: IO[E, B]): IO[E, (A, B)] = fea.zipPar(feb) 180 | 181 | override def cancelable[E, A](register: (Either[E, A] => Unit) => IO[Nothing, _]): IO[E, A] = 182 | //noinspection ScalaUnnecessaryParentheses 183 | IO.effectAsyncInterrupt { (cbForZio: IO[E, A] => Unit) => 184 | val cancellationToken: IO[Nothing, _] = register { result => 185 | cbForZio(IO.fromEither(result)) 186 | } 187 | 188 | Left(cancellationToken) 189 | } 190 | } 191 | 192 | } 193 | 194 | trait ZioInstances { 195 | 196 | implicit val zioInstance: ZioInstanceMixins.ZioBifunctor 197 | with ZioInstanceMixins.ZioBMonad 198 | with ZioInstanceMixins.ZioBME 199 | with ZioInstanceMixins.ZioBracket 200 | with ZioInstanceMixins.ZioDie 201 | with ZioInstanceMixins.ZioSync 202 | with ZioInstanceMixins.ZioAsync 203 | with ZioInstanceMixins.ZioTimer 204 | with ZioInstanceMixins.ZioConcurrent 205 | with Calendar.Live[IO] 206 | with Console.Live[IO] 207 | with EnvVars.Live[IO] 208 | with Resources.Live[IO] = 209 | new ZioInstanceMixins.ZioBifunctor with ZioInstanceMixins.ZioBMonad with ZioInstanceMixins.ZioBME 210 | with ZioInstanceMixins.ZioBracket with ZioInstanceMixins.ZioDie with ZioInstanceMixins.ZioSync 211 | with ZioInstanceMixins.ZioAsync with ZioInstanceMixins.ZioTimer with ZioInstanceMixins.ZioConcurrent 212 | with Calendar.Live[IO] with Console.Live[IO] with EnvVars.Live[IO] with Resources.Live[IO] 213 | 214 | } 215 | -------------------------------------------------------------------------------- /testing/src/main/scala/au/id/tmm/bfect/testing/BState.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.testing 17 | 18 | import java.time.{Duration, Instant} 19 | 20 | import au.id.tmm.bfect.effects.extra.Resources 21 | import au.id.tmm.bfect.effects.{Async, Bracket, Concurrent, Timer} 22 | import au.id.tmm.bfect.{BME, ExitCase, Failure, Fibre} 23 | 24 | import scala.util.Random 25 | 26 | final case class BState[S, +E, +A](run: S => (S, Either[E, A])) { 27 | 28 | def flatten[E2 >: E, A2](implicit ev: A <:< BState[S, E2, A2]): BState[S, E2, A2] = 29 | BState( 30 | run andThen { 31 | case (state, Right(bState)) => bState.run(state) 32 | case (state, Left(e)) => (state, Left(e)) 33 | }, 34 | ) 35 | 36 | def biMap[E2, A2](leftF: E => E2, rightF: A => A2): BState[S, E2, A2] = 37 | BState( 38 | run andThen { 39 | case (state, Right(a)) => (state, Right(rightF(a))) 40 | case (state, Left(e)) => (state, Left(leftF(e))) 41 | }, 42 | ) 43 | 44 | def leftMap[E2](f: E => E2): BState[S, E2, A] = biMap(f, identity) 45 | 46 | def map[A2](f: A => A2): BState[S, E, A2] = biMap(identity, f) 47 | 48 | def flatMap[E2 >: E, A2](f: A => BState[S, E2, A2]): BState[S, E2, A2] = map[BState[S, E2, A2]](f).flatten[E2, A2] 49 | 50 | def runS(s0: S): S = run(s0) match { case (s, _) => s } 51 | 52 | def runEither(s0: S): Either[E, A] = run(s0) match { case (_, either) => either } 53 | 54 | def runUnsafe(s0: S): A = runEither(s0) match { 55 | case Right(a) => a 56 | case Left(t: Throwable) => throw t 57 | case Left(e) => throw new Exception(s"Run failed due to $e") 58 | } 59 | 60 | def statelessRun(implicit ev: Unit =:= S): Either[E, A] = run(())._2 61 | 62 | def statelessRunUnsafe(implicit ev: Unit =:= S): A = runUnsafe(()) 63 | 64 | } 65 | 66 | object BState { 67 | 68 | type Stateless[+E, +A] = BState[Unit, E, A] 69 | 70 | def pure[S, A](a: A): BState[S, Nothing, A] = BState(s => (s, Right(a))) 71 | 72 | def leftPure[S, E](e: E): BState[S, E, Nothing] = BState(s => (s, Left(e))) 73 | 74 | def modify[S](f: S => S): BState[S, Nothing, Unit] = BState(s => (f(s), Right(()))) 75 | 76 | def inspect[S, E, A](f: S => Either[E, A]): BState[S, E, A] = BState(s => (s, f(s))) 77 | 78 | def rightInspect[S, A](f: S => A): BState[S, Nothing, A] = inspect(f.andThen(Right.apply)) 79 | 80 | trait BMEInstance[S] extends BME[BState[S, +*, +*]] { 81 | override def rightPure[E, A](a: A): BState[S, E, A] = BState.pure(a) 82 | 83 | override def leftPure[E, A](e: E): BState[S, E, A] = BState.leftPure(e) 84 | 85 | override def flatMap[E1, E2 >: E1, A, B](fe1a: BState[S, E1, A])(fafe2b: A => BState[S, E2, B]): BState[S, E2, B] = 86 | fe1a.flatMap(fafe2b) 87 | 88 | override def tailRecM[E, A, A1](a: A)(f: A => BState[S, E, Either[A, A1]]): BState[S, E, A1] = f(a).flatMap { 89 | case Right(value) => pure(value) 90 | case Left(value) => tailRecM(value)(f) 91 | } 92 | 93 | override def biMap[L1, R1, L2, R2](f: BState[S, L1, R1])(leftF: L1 => L2, rightF: R1 => R2): BState[S, L2, R2] = 94 | f.biMap(leftF, rightF) 95 | } 96 | 97 | trait ConcurrentInstance[S] 98 | extends BMEInstance[S] 99 | with Concurrent.WithBMonad[BState[S, +*, +*]] 100 | with Async[BState[S, +*, +*]] 101 | with Bracket.WithBMonad[BState[S, +*, +*]] 102 | with Timer.WithBMonad[BState[S, +*, +*]] { 103 | 104 | private def asFibre[E, A](fea: BState[S, E, A]): Fibre[BState[S, +*, +*], E, A] = 105 | new Fibre[BState[S, +*, +*], E, A] { 106 | override def cancel: BState[S, Nothing, Unit] = BState[S, Nothing, Unit](d => (d, Right(()))) 107 | 108 | override def join: BState[S, E, A] = fea 109 | } 110 | 111 | override def start[E, A](fea: BState[S, E, A]): BState[S, Nothing, Fibre[BState[S, +*, +*], E, A]] = 112 | pure(asFibre(fea)) 113 | 114 | override def racePair[E, A, B]( 115 | fea: BState[S, E, A], 116 | feb: BState[S, E, B], 117 | ): BState[S, E, Either[(A, Fibre[BState[S, +*, +*], E, B]), (Fibre[BState[S, +*, +*], E, A], B)]] = 118 | if (Random.nextBoolean()) { 119 | fea.map(a => Left((a, asFibre(feb)))) 120 | } else { 121 | feb.map(b => Right((asFibre(fea), b))) 122 | } 123 | 124 | override def cancelable[E, A](k: (Either[E, A] => Unit) => BState[S, Nothing, _]): BState[S, E, A] = asyncF(k) 125 | 126 | override def asyncF[E, A](k: (Either[E, A] => Unit) => BState[S, Nothing, _]): BState[S, E, A] = { 127 | var result: Option[Either[E, A]] = None 128 | 129 | k { 130 | case r @ Right(a) => result = Some(r) 131 | case l @ Left(e) => result = Some(l) 132 | } 133 | 134 | BState[S, Nothing, BState[S, E, A]] { state => 135 | result match { 136 | case Some(Right(a)) => (state, Right(BState.pure(a))) 137 | case Some(Left(e)) => (state, Right(BState.leftPure[S, E](e))) 138 | case None => (state, Right(never)) 139 | } 140 | }.flatten[E, A] 141 | } 142 | 143 | override def never: BState[S, Nothing, Nothing] = BState { _ => 144 | throw new IllegalStateException("never") 145 | } 146 | 147 | override def suspend[E, A](effect: => BState[S, E, A]): BState[S, E, A] = 148 | BState[S, E, BState[S, E, A]](s => (s, Right(effect))).flatten[E, A] 149 | 150 | override def bracketCase[R, E, A]( 151 | acquire: BState[S, E, R], 152 | release: (R, ExitCase[E, Unit]) => BState[S, Nothing, _], 153 | use: R => BState[S, E, A], 154 | ): BState[S, E, A] = 155 | BState { state => 156 | val (stateAfterAcquisition, result) = acquire.run(state) 157 | 158 | result match { 159 | case Right(acquired) => 160 | use(acquired).run(stateAfterAcquisition) match { 161 | case (stateAfterUse, Right(resultAfterUse)) => 162 | release(acquired, ExitCase.Succeeded(())).map(_ => resultAfterUse).run(stateAfterUse) 163 | 164 | case (stateAfterUse, Left(error)) => 165 | release(acquired, ExitCase.Failed(Failure.Checked(error))) 166 | .flatMap(_ => leftPure(error)) 167 | .run(stateAfterUse) 168 | } 169 | case Left(acquisitionFailure) => (stateAfterAcquisition, Left(acquisitionFailure)) 170 | } 171 | } 172 | 173 | override def handleErrorWith[E1, A, E2](fea: BState[S, E1, A])(f: E1 => BState[S, E2, A]): BState[S, E2, A] = 174 | BState[S, E2, A]( 175 | fea.run andThen { 176 | case (state, result) => { 177 | result match { 178 | case Right(a) => (state, Right(a)) 179 | case Left(e) => f(e).run(state) 180 | } 181 | } 182 | }, 183 | ) 184 | 185 | override def sleep(duration: Duration): BState[S, Nothing, Unit] = 186 | BState(state => (applySleepToState(duration, state), Right(()))) 187 | 188 | override def now: BState[S, Nothing, Instant] = 189 | BState { state => 190 | nowFromState(state) match { 191 | case (newState, instant) => (newState, Right(instant)) 192 | } 193 | } 194 | 195 | protected def nowFromState(state: S): (S, Instant) 196 | 197 | protected def applySleepToState(sleepDuration: Duration, state: S): S 198 | } 199 | 200 | trait TimerInstance[S] extends ConcurrentInstance[S] with Timer.WithBMonad[BState[S, +*, +*]] { 201 | protected def nowFromState(state: S): (S, Instant) 202 | 203 | protected def applySleepToState(sleepDuration: Duration, state: S): S 204 | 205 | override def sleep(duration: Duration): BState[S, Nothing, Unit] = 206 | BState(state => (applySleepToState(duration, state), Right(()))) 207 | 208 | override def now: BState[S, Nothing, Instant] = 209 | BState { state => 210 | nowFromState(state) match { 211 | case (newState, instant) => (newState, Right(instant)) 212 | } 213 | } 214 | } 215 | 216 | /** 217 | * Includes: 218 | * - an instance for `Timer` where `now` is always 1970-01-01T00:00:00Z, and sleep does 219 | * not alter the state. 220 | * - an instance for `Resources` that reads the live resources 221 | */ 222 | trait CompleteConcurrentInstance[S] extends ConcurrentInstance[S] with Resources.Live[BState[S, +*, +*]] { 223 | override def nowFromState(state: S): (S, Instant) = (state, Instant.EPOCH) 224 | override def applySleepToState(sleepDuration: Duration, state: S): S = state 225 | } 226 | 227 | implicit def concurrentInstance[S]: Concurrent[BState[S, +*, +*]] 228 | with Concurrent[BState[S, +*, +*]] 229 | with Async[BState[S, +*, +*]] 230 | with Bracket[BState[S, +*, +*]] 231 | with Timer[BState[S, +*, +*]] 232 | with Resources[BState[S, +*, +*]] = new CompleteConcurrentInstance[S] {} 233 | 234 | } 235 | -------------------------------------------------------------------------------- /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. 202 | -------------------------------------------------------------------------------- /core/src/main/scala/au/id/tmm/bfect/effects/Concurrent.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Timothy McCarthy 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package au.id.tmm.bfect.effects 17 | 18 | import au.id.tmm.bfect._ 19 | 20 | trait Concurrent[F[_, _]] { 21 | 22 | def start[E, A](fea: F[E, A]): F[Nothing, Fibre[F, E, A]] 23 | 24 | def fork[E, A](fea: F[E, A]): F[Nothing, Fibre[F, E, A]] = start(fea) 25 | 26 | def racePair[E, A, B](fea: F[E, A], feb: F[E, B]): F[E, Either[(A, Fibre[F, E, B]), (Fibre[F, E, A], B)]] 27 | 28 | def race[E, A, B](fea: F[E, A], feb: F[E, B]): F[E, Either[A, B]] 29 | 30 | def par[E, A, B](fea: F[E, A], feb: F[E, B]): F[E, (A, B)] 31 | 32 | def cancelable[E, A](k: (Either[E, A] => Unit) => F[Nothing, _]): F[E, A] 33 | 34 | } 35 | 36 | object Concurrent extends ConcurrentStaticOps with ConcurrentParNs { 37 | 38 | def apply[F[_, _] : Concurrent]: Concurrent[F] = implicitly[Concurrent[F]] 39 | 40 | trait WithBMonad[F[_, _]] extends Concurrent[F] { self: BMonad[F] => 41 | override def race[E, A, B](fea: F[E, A], feb: F[E, B]): F[E, Either[A, B]] = 42 | /*_*/ 43 | flatMap(racePair(fea, feb)) { 44 | case Left((a, fibreForB)) => map[E, Unit, Either[A, B]](leftWiden(fibreForB.cancel))(_ => Left(a)) 45 | case Right((fibreForA, b)) => map[E, Unit, Either[A, B]](leftWiden(fibreForA.cancel))(_ => Right(b)) 46 | } 47 | /*_*/ 48 | 49 | override def par[E, A, B](fea: F[E, A], feb: F[E, B]): F[E, (A, B)] = 50 | flatMap(racePair(fea, feb)) { 51 | case Left((a, bFibre)) => map(bFibre.join)(b => (a, b)) 52 | case Right((aFibre, b)) => map(aFibre.join)(a => (a, b)) 53 | } 54 | } 55 | 56 | trait ToConcurrentOps { 57 | implicit def toConcurrentOps[F[_, _], E, A](fea: F[E, A])(implicit timerInstance: Concurrent[F]): Ops[F, E, A] = 58 | new Ops[F, E, A](fea) 59 | 60 | implicit def toConcurrentOpsErrorNothing[F[_, _], A]( 61 | fea: F[Nothing, A], 62 | )(implicit 63 | timerInstance: Concurrent[F], 64 | ): Ops[F, Nothing, A] = 65 | new Ops[F, Nothing, A](fea) 66 | 67 | implicit def toConcurrentOpsValueNothing[F[_, _], E]( 68 | fea: F[E, Nothing], 69 | )(implicit 70 | timerInstance: Concurrent[F], 71 | ): Ops[F, E, Nothing] = 72 | new Ops[F, E, Nothing](fea) 73 | 74 | implicit def toConcurrentOpsErrorNothingValueNothing[F[_, _]]( 75 | fea: F[Nothing, Nothing], 76 | )(implicit 77 | timerInstance: Concurrent[F], 78 | ): Ops[F, Nothing, Nothing] = 79 | new Ops[F, Nothing, Nothing](fea) 80 | } 81 | 82 | implicit class Ops[F[_, _], E, A](protected val fea: F[E, A])(implicit concurrent: Concurrent[F]) { 83 | def start: F[Nothing, Fibre[F, E, A]] = concurrent.start(fea) 84 | def fork: F[Nothing, Fibre[F, E, A]] = concurrent.fork(fea) 85 | 86 | def race[B](feb: F[E, B]): F[E, Either[A, B]] = concurrent.race(fea, feb) 87 | } 88 | 89 | } 90 | 91 | trait ConcurrentStaticOps { 92 | def race[F[_, _] : Concurrent, E, A, B](fea: F[E, A], feb: F[E, B]): F[E, Either[A, B]] = 93 | Concurrent[F].race(fea, feb) 94 | } 95 | 96 | trait ConcurrentParNs { 97 | def par10[F[_, _] : Concurrent : BFunctor, E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]( 98 | fetchTally1: F[E, T1], 99 | fetchTally2: F[E, T2], 100 | fetchTally3: F[E, T3], 101 | fetchTally4: F[E, T4], 102 | fetchTally5: F[E, T5], 103 | fetchTally6: F[E, T6], 104 | fetchTally7: F[E, T7], 105 | fetchTally8: F[E, T8], 106 | fetchTally9: F[E, T9], 107 | fetchTally10: F[E, T10], 108 | ): F[E, (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)] = { 109 | val concurrentInstance = Concurrent[F] 110 | 111 | BFunctor[F].map( 112 | concurrentInstance.par( 113 | fetchTally1, 114 | concurrentInstance.par( 115 | fetchTally2, 116 | concurrentInstance.par( 117 | fetchTally3, 118 | concurrentInstance.par( 119 | fetchTally4, 120 | concurrentInstance.par( 121 | fetchTally5, 122 | concurrentInstance.par( 123 | fetchTally6, 124 | concurrentInstance.par( 125 | fetchTally7, 126 | concurrentInstance.par(fetchTally8, concurrentInstance.par(fetchTally9, fetchTally10)))), 127 | ), 128 | ), 129 | ), 130 | ), 131 | )) { 132 | case (tally1, (tally2, (tally3, (tally4, (tally5, (tally6, (tally7, (tally8, (tally9, tally10))))))))) => 133 | ( 134 | tally1, 135 | tally2, 136 | tally3, 137 | tally4, 138 | tally5, 139 | tally6, 140 | tally7, 141 | tally8, 142 | tally9, 143 | tally10, 144 | ) 145 | } 146 | } 147 | 148 | def par9[F[_, _] : Concurrent : BFunctor, E, T1, T2, T3, T4, T5, T6, T7, T8, T9]( 149 | fetchTally1: F[E, T1], 150 | fetchTally2: F[E, T2], 151 | fetchTally3: F[E, T3], 152 | fetchTally4: F[E, T4], 153 | fetchTally5: F[E, T5], 154 | fetchTally6: F[E, T6], 155 | fetchTally7: F[E, T7], 156 | fetchTally8: F[E, T8], 157 | fetchTally9: F[E, T9], 158 | ): F[E, (T1, T2, T3, T4, T5, T6, T7, T8, T9)] = { 159 | val concurrentInstance = Concurrent[F] 160 | 161 | BFunctor[F].map( 162 | concurrentInstance.par( 163 | fetchTally1, 164 | concurrentInstance.par( 165 | fetchTally2, 166 | concurrentInstance.par( 167 | fetchTally3, 168 | concurrentInstance.par( 169 | fetchTally4, 170 | concurrentInstance.par( 171 | fetchTally5, 172 | concurrentInstance.par( 173 | fetchTally6, 174 | concurrentInstance.par(fetchTally7, concurrentInstance.par(fetchTally8, fetchTally9)))), 175 | ), 176 | ), 177 | ), 178 | )) { 179 | case (tally1, (tally2, (tally3, (tally4, (tally5, (tally6, (tally7, (tally8, tally9)))))))) => 180 | ( 181 | tally1, 182 | tally2, 183 | tally3, 184 | tally4, 185 | tally5, 186 | tally6, 187 | tally7, 188 | tally8, 189 | tally9, 190 | ) 191 | } 192 | } 193 | 194 | def par8[F[_, _] : Concurrent : BFunctor, E, T1, T2, T3, T4, T5, T6, T7, T8]( 195 | fetchTally1: F[E, T1], 196 | fetchTally2: F[E, T2], 197 | fetchTally3: F[E, T3], 198 | fetchTally4: F[E, T4], 199 | fetchTally5: F[E, T5], 200 | fetchTally6: F[E, T6], 201 | fetchTally7: F[E, T7], 202 | fetchTally8: F[E, T8], 203 | ): F[E, (T1, T2, T3, T4, T5, T6, T7, T8)] = { 204 | val concurrentInstance = Concurrent[F] 205 | 206 | BFunctor[F].map( 207 | concurrentInstance.par( 208 | fetchTally1, 209 | concurrentInstance.par( 210 | fetchTally2, 211 | concurrentInstance.par( 212 | fetchTally3, 213 | concurrentInstance.par( 214 | fetchTally4, 215 | concurrentInstance.par( 216 | fetchTally5, 217 | concurrentInstance.par(fetchTally6, concurrentInstance.par(fetchTally7, fetchTally8)))), 218 | ), 219 | ), 220 | )) { 221 | case (tally1, (tally2, (tally3, (tally4, (tally5, (tally6, (tally7, tally8))))))) => 222 | ( 223 | tally1, 224 | tally2, 225 | tally3, 226 | tally4, 227 | tally5, 228 | tally6, 229 | tally7, 230 | tally8, 231 | ) 232 | } 233 | } 234 | 235 | def par7[F[_, _] : Concurrent : BFunctor, E, T1, T2, T3, T4, T5, T6, T7]( 236 | fetchTally1: F[E, T1], 237 | fetchTally2: F[E, T2], 238 | fetchTally3: F[E, T3], 239 | fetchTally4: F[E, T4], 240 | fetchTally5: F[E, T5], 241 | fetchTally6: F[E, T6], 242 | fetchTally7: F[E, T7], 243 | ): F[E, (T1, T2, T3, T4, T5, T6, T7)] = { 244 | val concurrentInstance = Concurrent[F] 245 | 246 | BFunctor[F].map( 247 | concurrentInstance.par( 248 | fetchTally1, 249 | concurrentInstance.par( 250 | fetchTally2, 251 | concurrentInstance.par( 252 | fetchTally3, 253 | concurrentInstance 254 | .par(fetchTally4, concurrentInstance.par(fetchTally5, concurrentInstance.par(fetchTally6, fetchTally7)))), 255 | ), 256 | )) { 257 | case (tally1, (tally2, (tally3, (tally4, (tally5, (tally6, tally7)))))) => 258 | ( 259 | tally1, 260 | tally2, 261 | tally3, 262 | tally4, 263 | tally5, 264 | tally6, 265 | tally7, 266 | ) 267 | } 268 | } 269 | 270 | def par6[F[_, _] : Concurrent : BFunctor, E, T1, T2, T3, T4, T5, T6]( 271 | fetchTally1: F[E, T1], 272 | fetchTally2: F[E, T2], 273 | fetchTally3: F[E, T3], 274 | fetchTally4: F[E, T4], 275 | fetchTally5: F[E, T5], 276 | fetchTally6: F[E, T6], 277 | ): F[E, (T1, T2, T3, T4, T5, T6)] = { 278 | val concurrentInstance = Concurrent[F] 279 | 280 | BFunctor[F].map( 281 | concurrentInstance.par( 282 | fetchTally1, 283 | concurrentInstance.par( 284 | fetchTally2, 285 | concurrentInstance 286 | .par(fetchTally3, concurrentInstance.par(fetchTally4, concurrentInstance.par(fetchTally5, fetchTally6)))), 287 | )) { 288 | case (tally1, (tally2, (tally3, (tally4, (tally5, tally6))))) => 289 | ( 290 | tally1, 291 | tally2, 292 | tally3, 293 | tally4, 294 | tally5, 295 | tally6, 296 | ) 297 | } 298 | } 299 | 300 | def par5[F[_, _] : Concurrent : BFunctor, E, T1, T2, T3, T4, T5]( 301 | fetchTally1: F[E, T1], 302 | fetchTally2: F[E, T2], 303 | fetchTally3: F[E, T3], 304 | fetchTally4: F[E, T4], 305 | fetchTally5: F[E, T5], 306 | ): F[E, (T1, T2, T3, T4, T5)] = { 307 | val concurrentInstance = Concurrent[F] 308 | 309 | BFunctor[F].map( 310 | concurrentInstance.par( 311 | fetchTally1, 312 | concurrentInstance 313 | .par(fetchTally2, concurrentInstance.par(fetchTally3, concurrentInstance.par(fetchTally4, fetchTally5))))) { 314 | case (tally1, (tally2, (tally3, (tally4, tally5)))) => 315 | ( 316 | tally1, 317 | tally2, 318 | tally3, 319 | tally4, 320 | tally5, 321 | ) 322 | } 323 | } 324 | 325 | def par4[F[_, _] : Concurrent : BFunctor, E, T1, T2, T3, T4]( 326 | fetchTally1: F[E, T1], 327 | fetchTally2: F[E, T2], 328 | fetchTally3: F[E, T3], 329 | fetchTally4: F[E, T4], 330 | ): F[E, (T1, T2, T3, T4)] = { 331 | val concurrentInstance = Concurrent[F] 332 | 333 | BFunctor[F].map( 334 | concurrentInstance 335 | .par(fetchTally1, concurrentInstance.par(fetchTally2, concurrentInstance.par(fetchTally3, fetchTally4)))) { 336 | case (tally1, (tally2, (tally3, tally4))) => 337 | ( 338 | tally1, 339 | tally2, 340 | tally3, 341 | tally4, 342 | ) 343 | } 344 | } 345 | 346 | def par3[F[_, _] : Concurrent : BFunctor, E, T1, T2, T3]( 347 | fetchTally1: F[E, T1], 348 | fetchTally2: F[E, T2], 349 | fetchTally3: F[E, T3], 350 | ): F[E, (T1, T2, T3)] = { 351 | val concurrentInstance = Concurrent[F] 352 | 353 | BFunctor[F].map(concurrentInstance.par(fetchTally1, concurrentInstance.par(fetchTally2, fetchTally3))) { 354 | case (tally1, (tally2, tally3)) => 355 | ( 356 | tally1, 357 | tally2, 358 | tally3, 359 | ) 360 | } 361 | } 362 | 363 | def par2[F[_, _] : Concurrent : BFunctor, E, T1, T2]( 364 | fetchTally1: F[E, T1], 365 | fetchTally2: F[E, T2], 366 | ): F[E, (T1, T2)] = { 367 | val concurrentInstance = Concurrent[F] 368 | 369 | BFunctor[F].map(concurrentInstance.par(fetchTally1, fetchTally2)) { 370 | case (tally1, tally2) => 371 | ( 372 | tally1, 373 | tally2, 374 | ) 375 | } 376 | } 377 | 378 | } 379 | -------------------------------------------------------------------------------- /.gradletasknamecache: -------------------------------------------------------------------------------- 1 | assemble 2 | bfect-core:assemble 3 | bfect-extra-effects:assemble 4 | bfect-io:assemble 5 | interop:bfect-interop-cats:assemble 6 | interop:bfect-interop-zio:assemble 7 | build 8 | bfect-core:build 9 | bfect-extra-effects:build 10 | bfect-io:build 11 | interop:bfect-interop-cats:build 12 | interop:bfect-interop-zio:build 13 | buildDependents 14 | bfect-core:buildDependents 15 | bfect-extra-effects:buildDependents 16 | bfect-io:buildDependents 17 | interop:bfect-interop-cats:buildDependents 18 | interop:bfect-interop-zio:buildDependents 19 | buildNeeded 20 | bfect-core:buildNeeded 21 | bfect-extra-effects:buildNeeded 22 | bfect-io:buildNeeded 23 | interop:bfect-interop-cats:buildNeeded 24 | interop:bfect-interop-zio:buildNeeded 25 | classes 26 | bfect-core:classes 27 | bfect-extra-effects:classes 28 | bfect-io:classes 29 | interop:bfect-interop-cats:classes 30 | interop:bfect-interop-zio:classes 31 | clean 32 | bfect-core:clean 33 | bfect-extra-effects:clean 34 | bfect-io:clean 35 | interop:bfect-interop-cats:clean 36 | interop:bfect-interop-zio:clean 37 | jar 38 | bfect-core:jar 39 | bfect-extra-effects:jar 40 | bfect-io:jar 41 | interop:bfect-interop-cats:jar 42 | interop:bfect-interop-zio:jar 43 | scoverageClasses 44 | bfect-core:scoverageClasses 45 | bfect-extra-effects:scoverageClasses 46 | bfect-io:scoverageClasses 47 | interop:bfect-interop-cats:scoverageClasses 48 | interop:bfect-interop-zio:scoverageClasses 49 | testClasses 50 | bfect-core:testClasses 51 | bfect-extra-effects:testClasses 52 | bfect-io:testClasses 53 | interop:bfect-interop-cats:testClasses 54 | interop:bfect-interop-zio:testClasses 55 | init 56 | wrapper 57 | javadoc 58 | bfect-core:javadoc 59 | bfect-extra-effects:javadoc 60 | bfect-io:javadoc 61 | interop:bfect-interop-cats:javadoc 62 | interop:bfect-interop-zio:javadoc 63 | scaladoc 64 | bfect-core:scaladoc 65 | bfect-extra-effects:scaladoc 66 | bfect-io:scaladoc 67 | interop:bfect-interop-cats:scaladoc 68 | interop:bfect-interop-zio:scaladoc 69 | buildEnvironment 70 | bfect-core:buildEnvironment 71 | bfect-extra-effects:buildEnvironment 72 | bfect-io:buildEnvironment 73 | interop:buildEnvironment 74 | interop:bfect-interop-cats:buildEnvironment 75 | interop:bfect-interop-zio:buildEnvironment 76 | components 77 | bfect-core:components 78 | bfect-extra-effects:components 79 | bfect-io:components 80 | interop:components 81 | interop:bfect-interop-cats:components 82 | interop:bfect-interop-zio:components 83 | dependencies 84 | bfect-core:dependencies 85 | bfect-extra-effects:dependencies 86 | bfect-io:dependencies 87 | interop:dependencies 88 | interop:bfect-interop-cats:dependencies 89 | interop:bfect-interop-zio:dependencies 90 | dependencyInsight 91 | bfect-core:dependencyInsight 92 | bfect-extra-effects:dependencyInsight 93 | bfect-io:dependencyInsight 94 | interop:dependencyInsight 95 | interop:bfect-interop-cats:dependencyInsight 96 | interop:bfect-interop-zio:dependencyInsight 97 | dependentComponents 98 | bfect-core:dependentComponents 99 | bfect-extra-effects:dependentComponents 100 | bfect-io:dependentComponents 101 | interop:dependentComponents 102 | interop:bfect-interop-cats:dependentComponents 103 | interop:bfect-interop-zio:dependentComponents 104 | help 105 | bfect-core:help 106 | bfect-extra-effects:help 107 | bfect-io:help 108 | interop:help 109 | interop:bfect-interop-cats:help 110 | interop:bfect-interop-zio:help 111 | model 112 | bfect-core:model 113 | bfect-extra-effects:model 114 | bfect-io:model 115 | interop:model 116 | interop:bfect-interop-cats:model 117 | interop:bfect-interop-zio:model 118 | projects 119 | bfect-core:projects 120 | bfect-extra-effects:projects 121 | bfect-io:projects 122 | interop:projects 123 | interop:bfect-interop-cats:projects 124 | interop:bfect-interop-zio:projects 125 | properties 126 | bfect-core:properties 127 | bfect-extra-effects:properties 128 | bfect-io:properties 129 | interop:properties 130 | interop:bfect-interop-cats:properties 131 | interop:bfect-interop-zio:properties 132 | tasks 133 | bfect-core:tasks 134 | bfect-extra-effects:tasks 135 | bfect-io:tasks 136 | interop:tasks 137 | interop:bfect-interop-cats:tasks 138 | interop:bfect-interop-zio:tasks 139 | cleanIdea 140 | idea 141 | openIdea 142 | reckonTagCreate 143 | reckonTagPush 144 | closeAndPromoteRepository 145 | closeAndReleaseRepository 146 | closeRepository 147 | getStagingProfile 148 | promoteRepository 149 | releaseRepository 150 | bfect-core:uploadArchives 151 | bfect-extra-effects:uploadArchives 152 | bfect-io:uploadArchives 153 | interop:bfect-interop-cats:uploadArchives 154 | interop:bfect-interop-zio:uploadArchives 155 | check 156 | bfect-core:check 157 | bfect-extra-effects:check 158 | bfect-io:check 159 | interop:bfect-interop-cats:check 160 | interop:bfect-interop-zio:check 161 | checkScoverage 162 | bfect-core:checkScoverage 163 | bfect-extra-effects:checkScoverage 164 | bfect-io:checkScoverage 165 | interop:bfect-interop-cats:checkScoverage 166 | interop:bfect-interop-zio:checkScoverage 167 | reportScoverage 168 | bfect-core:reportScoverage 169 | bfect-extra-effects:reportScoverage 170 | bfect-io:reportScoverage 171 | interop:bfect-interop-cats:reportScoverage 172 | interop:bfect-interop-zio:reportScoverage 173 | test 174 | bfect-core:test 175 | bfect-extra-effects:test 176 | bfect-io:test 177 | interop:bfect-interop-cats:test 178 | interop:bfect-interop-zio:test 179 | testScoverage 180 | bfect-core:testScoverage 181 | bfect-extra-effects:testScoverage 182 | bfect-io:testScoverage 183 | interop:bfect-interop-cats:testScoverage 184 | interop:bfect-interop-zio:testScoverage 185 | bfect-core:updateLicenses 186 | bfect-extra-effects:updateLicenses 187 | bfect-io:updateLicenses 188 | interop:bfect-interop-cats:updateLicenses 189 | interop:bfect-interop-zio:updateLicenses 190 | cleanIdeaModule 191 | cleanIdeaProject 192 | cleanIdeaWorkspace 193 | compileJava 194 | bfect-core:compileJava 195 | bfect-extra-effects:compileJava 196 | bfect-io:compileJava 197 | interop:bfect-interop-cats:compileJava 198 | interop:bfect-interop-zio:compileJava 199 | compileScala 200 | bfect-core:compileScala 201 | bfect-extra-effects:compileScala 202 | bfect-io:compileScala 203 | interop:bfect-interop-cats:compileScala 204 | interop:bfect-interop-zio:compileScala 205 | compileScoverageJava 206 | bfect-core:compileScoverageJava 207 | bfect-extra-effects:compileScoverageJava 208 | bfect-io:compileScoverageJava 209 | interop:bfect-interop-cats:compileScoverageJava 210 | interop:bfect-interop-zio:compileScoverageJava 211 | compileScoverageScala 212 | bfect-core:compileScoverageScala 213 | bfect-extra-effects:compileScoverageScala 214 | bfect-io:compileScoverageScala 215 | interop:bfect-interop-cats:compileScoverageScala 216 | interop:bfect-interop-zio:compileScoverageScala 217 | compileTestJava 218 | bfect-core:compileTestJava 219 | bfect-extra-effects:compileTestJava 220 | bfect-io:compileTestJava 221 | interop:bfect-interop-cats:compileTestJava 222 | interop:bfect-interop-zio:compileTestJava 223 | compileTestScala 224 | bfect-core:compileTestScala 225 | bfect-extra-effects:compileTestScala 226 | bfect-io:compileTestScala 227 | interop:bfect-interop-cats:compileTestScala 228 | interop:bfect-interop-zio:compileTestScala 229 | ideaModule 230 | ideaProject 231 | ideaWorkspace 232 | bfect-core:install 233 | bfect-extra-effects:install 234 | bfect-io:install 235 | interop:bfect-interop-cats:install 236 | interop:bfect-interop-zio:install 237 | jarScoverage 238 | bfect-core:jarScoverage 239 | bfect-extra-effects:jarScoverage 240 | bfect-io:jarScoverage 241 | interop:bfect-interop-cats:jarScoverage 242 | interop:bfect-interop-zio:jarScoverage 243 | bfect-core:javadocJar 244 | bfect-extra-effects:javadocJar 245 | bfect-io:javadocJar 246 | interop:bfect-interop-cats:javadocJar 247 | interop:bfect-interop-zio:javadocJar 248 | bfect-core:licenseFormatMain 249 | bfect-extra-effects:licenseFormatMain 250 | bfect-io:licenseFormatMain 251 | interop:bfect-interop-cats:licenseFormatMain 252 | interop:bfect-interop-zio:licenseFormatMain 253 | bfect-core:licenseFormatScoverage 254 | bfect-extra-effects:licenseFormatScoverage 255 | bfect-io:licenseFormatScoverage 256 | interop:bfect-interop-cats:licenseFormatScoverage 257 | interop:bfect-interop-zio:licenseFormatScoverage 258 | bfect-core:licenseFormatTest 259 | bfect-extra-effects:licenseFormatTest 260 | bfect-io:licenseFormatTest 261 | interop:bfect-interop-cats:licenseFormatTest 262 | interop:bfect-interop-zio:licenseFormatTest 263 | bfect-core:licenseMain 264 | bfect-extra-effects:licenseMain 265 | bfect-io:licenseMain 266 | interop:bfect-interop-cats:licenseMain 267 | interop:bfect-interop-zio:licenseMain 268 | bfect-core:licenseScoverage 269 | bfect-extra-effects:licenseScoverage 270 | bfect-io:licenseScoverage 271 | interop:bfect-interop-cats:licenseScoverage 272 | interop:bfect-interop-zio:licenseScoverage 273 | bfect-core:licenseTest 274 | bfect-extra-effects:licenseTest 275 | bfect-io:licenseTest 276 | interop:bfect-interop-cats:licenseTest 277 | interop:bfect-interop-zio:licenseTest 278 | processResources 279 | bfect-core:processResources 280 | bfect-extra-effects:processResources 281 | bfect-io:processResources 282 | interop:bfect-interop-cats:processResources 283 | interop:bfect-interop-zio:processResources 284 | processScoverageResources 285 | bfect-core:processScoverageResources 286 | bfect-extra-effects:processScoverageResources 287 | bfect-io:processScoverageResources 288 | interop:bfect-interop-cats:processScoverageResources 289 | interop:bfect-interop-zio:processScoverageResources 290 | processTestResources 291 | bfect-core:processTestResources 292 | bfect-extra-effects:processTestResources 293 | bfect-io:processTestResources 294 | interop:bfect-interop-cats:processTestResources 295 | interop:bfect-interop-zio:processTestResources 296 | bfect-core:releaseIfFinalVersion 297 | bfect-extra-effects:releaseIfFinalVersion 298 | bfect-io:releaseIfFinalVersion 299 | interop:bfect-interop-cats:releaseIfFinalVersion 300 | interop:bfect-interop-zio:releaseIfFinalVersion 301 | bfect-core:scaladocJar 302 | bfect-extra-effects:scaladocJar 303 | bfect-io:scaladocJar 304 | interop:bfect-interop-cats:scaladocJar 305 | interop:bfect-interop-zio:scaladocJar 306 | scoverage 307 | bfect-core:signArchives 308 | bfect-extra-effects:signArchives 309 | bfect-io:signArchives 310 | interop:bfect-interop-cats:signArchives 311 | interop:bfect-interop-zio:signArchives 312 | bfect-core:sourcesJar 313 | bfect-extra-effects:sourcesJar 314 | bfect-io:sourcesJar 315 | interop:bfect-interop-cats:sourcesJar 316 | interop:bfect-interop-zio:sourcesJar 317 | bfect-core:writeJavadocReadme 318 | bfect-extra-effects:writeJavadocReadme 319 | bfect-io:writeJavadocReadme 320 | interop:bfect-interop-cats:writeJavadocReadme 321 | interop:bfect-interop-zio:writeJavadocReadme 322 | Pattern: 323 | Pattern: 324 | Pattern: 325 | assemble 326 | assemble 327 | assemble 328 | assemble 329 | assemble 330 | assemble 331 | build 332 | build 333 | build 334 | build 335 | build 336 | build 337 | buildDependents 338 | buildDependents 339 | buildDependents 340 | buildDependents 341 | buildDependents 342 | buildDependents 343 | buildNeeded 344 | buildNeeded 345 | buildNeeded 346 | buildNeeded 347 | buildNeeded 348 | buildNeeded 349 | classes 350 | classes 351 | classes 352 | classes 353 | classes 354 | classes 355 | clean 356 | clean 357 | clean 358 | clean 359 | clean 360 | clean 361 | jar 362 | jar 363 | jar 364 | jar 365 | jar 366 | jar 367 | scoverageClasses 368 | scoverageClasses 369 | scoverageClasses 370 | scoverageClasses 371 | scoverageClasses 372 | scoverageClasses 373 | testClasses 374 | testClasses 375 | testClasses 376 | testClasses 377 | testClasses 378 | testClasses 379 | init 380 | wrapper 381 | javadoc 382 | javadoc 383 | javadoc 384 | javadoc 385 | javadoc 386 | javadoc 387 | scaladoc 388 | scaladoc 389 | scaladoc 390 | scaladoc 391 | scaladoc 392 | scaladoc 393 | buildEnvironment 394 | buildEnvironment 395 | buildEnvironment 396 | buildEnvironment 397 | buildEnvironment 398 | buildEnvironment 399 | buildEnvironment 400 | components 401 | components 402 | components 403 | components 404 | components 405 | components 406 | components 407 | dependencies 408 | dependencies 409 | dependencies 410 | dependencies 411 | dependencies 412 | dependencies 413 | dependencies 414 | dependencyInsight 415 | dependencyInsight 416 | dependencyInsight 417 | dependencyInsight 418 | dependencyInsight 419 | dependencyInsight 420 | dependencyInsight 421 | dependentComponents 422 | dependentComponents 423 | dependentComponents 424 | dependentComponents 425 | dependentComponents 426 | dependentComponents 427 | dependentComponents 428 | help 429 | help 430 | help 431 | help 432 | help 433 | help 434 | help 435 | model 436 | model 437 | model 438 | model 439 | model 440 | model 441 | model 442 | projects 443 | projects 444 | projects 445 | projects 446 | projects 447 | projects 448 | projects 449 | properties 450 | properties 451 | properties 452 | properties 453 | properties 454 | properties 455 | properties 456 | tasks 457 | tasks 458 | tasks 459 | tasks 460 | tasks 461 | tasks 462 | tasks 463 | cleanIdea 464 | idea 465 | openIdea 466 | reckonTagCreate 467 | reckonTagPush 468 | closeAndPromoteRepository 469 | closeAndReleaseRepository 470 | closeRepository 471 | getStagingProfile 472 | promoteRepository 473 | releaseRepository 474 | uploadArchives 475 | uploadArchives 476 | uploadArchives 477 | uploadArchives 478 | uploadArchives 479 | check 480 | check 481 | check 482 | check 483 | check 484 | check 485 | checkScoverage 486 | checkScoverage 487 | checkScoverage 488 | checkScoverage 489 | checkScoverage 490 | checkScoverage 491 | reportScoverage 492 | reportScoverage 493 | reportScoverage 494 | reportScoverage 495 | reportScoverage 496 | reportScoverage 497 | test 498 | test 499 | test 500 | test 501 | test 502 | test 503 | testScoverage 504 | testScoverage 505 | testScoverage 506 | testScoverage 507 | testScoverage 508 | testScoverage 509 | updateLicenses 510 | updateLicenses 511 | updateLicenses 512 | updateLicenses 513 | updateLicenses 514 | cleanIdeaModule 515 | cleanIdeaProject 516 | cleanIdeaWorkspace 517 | compileJava 518 | compileJava 519 | compileJava 520 | compileJava 521 | compileJava 522 | compileJava 523 | compileScala 524 | compileScala 525 | compileScala 526 | compileScala 527 | compileScala 528 | compileScala 529 | compileScoverageJava 530 | compileScoverageJava 531 | compileScoverageJava 532 | compileScoverageJava 533 | compileScoverageJava 534 | compileScoverageJava 535 | compileScoverageScala 536 | compileScoverageScala 537 | compileScoverageScala 538 | compileScoverageScala 539 | compileScoverageScala 540 | compileScoverageScala 541 | compileTestJava 542 | compileTestJava 543 | compileTestJava 544 | compileTestJava 545 | compileTestJava 546 | compileTestJava 547 | compileTestScala 548 | compileTestScala 549 | compileTestScala 550 | compileTestScala 551 | compileTestScala 552 | compileTestScala 553 | ideaModule 554 | ideaProject 555 | ideaWorkspace 556 | install 557 | install 558 | install 559 | install 560 | install 561 | jarScoverage 562 | jarScoverage 563 | jarScoverage 564 | jarScoverage 565 | jarScoverage 566 | jarScoverage 567 | javadocJar 568 | javadocJar 569 | javadocJar 570 | javadocJar 571 | javadocJar 572 | licenseFormatMain 573 | licenseFormatMain 574 | licenseFormatMain 575 | licenseFormatMain 576 | licenseFormatMain 577 | licenseFormatScoverage 578 | licenseFormatScoverage 579 | licenseFormatScoverage 580 | licenseFormatScoverage 581 | licenseFormatScoverage 582 | licenseFormatTest 583 | licenseFormatTest 584 | licenseFormatTest 585 | licenseFormatTest 586 | licenseFormatTest 587 | licenseMain 588 | licenseMain 589 | licenseMain 590 | licenseMain 591 | licenseMain 592 | licenseScoverage 593 | licenseScoverage 594 | licenseScoverage 595 | licenseScoverage 596 | licenseScoverage 597 | licenseTest 598 | licenseTest 599 | licenseTest 600 | licenseTest 601 | licenseTest 602 | processResources 603 | processResources 604 | processResources 605 | processResources 606 | processResources 607 | processResources 608 | processScoverageResources 609 | processScoverageResources 610 | processScoverageResources 611 | processScoverageResources 612 | processScoverageResources 613 | processScoverageResources 614 | processTestResources 615 | processTestResources 616 | processTestResources 617 | processTestResources 618 | processTestResources 619 | processTestResources 620 | releaseIfFinalVersion 621 | releaseIfFinalVersion 622 | releaseIfFinalVersion 623 | releaseIfFinalVersion 624 | releaseIfFinalVersion 625 | scaladocJar 626 | scaladocJar 627 | scaladocJar 628 | scaladocJar 629 | scaladocJar 630 | scoverage 631 | signArchives 632 | signArchives 633 | signArchives 634 | signArchives 635 | signArchives 636 | sourcesJar 637 | sourcesJar 638 | sourcesJar 639 | sourcesJar 640 | sourcesJar 641 | writeJavadocReadme 642 | writeJavadocReadme 643 | writeJavadocReadme 644 | writeJavadocReadme 645 | writeJavadocReadme 646 | --------------------------------------------------------------------------------