Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -307,30 +307,16 @@ class SyntaxTest extends AnyWordSpec {
} yield ()
}
def z[F[+_, +_]: Error2]: F[String, Unit] = {
// Scala 3 Workaround
import izumi.functional.bio.WithFilter.WithFilterString
// Scala 3 Workaround

for {
case (1, 2) <- F.pure((2, 1)).widen[Any].widenError[String]
} yield ()
}
def xx[F[+_, +_]: Error2]: F[Unit, Unit] = {
// Scala 3 Workaround
import izumi.functional.bio.WithFilter.WithFilterUnit
// Scala 3 Workaround

for {
case (1, 2) <- F.pure((2, 1)).widen[Any].widenError[Unit]
} yield ()
}
def yy[F[+_, +_]: Error2]: F[Option[Throwable], Unit] = {
// Scala 3 Workaround
import izumi.functional.bio.WithFilter
implicit val withFilterScala3Workaround: WithFilter[Option[Throwable]] = WithFilter.WithFilterOption(using WithFilter.WithFilterNoSuchElementException)
val _ = withFilterScala3Workaround
// Scala 3 Workaround

for {
case (1, 2) <- F.pure((2, 1)).widen[Any].widenError[Option[Throwable]]
} yield ()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package izumi.functional.bio.syntax

/**
* Empty stub traits mirroring the Scala-3-only extension-method traits of the
* same name. They exist solely so that typeclass traits such as
* [[izumi.functional.bio.Monad2]] can use `... with Monad2ExtensionMethods`
* in a shared (`src/main/scala`) source without breaking Scala 2 compilation.
*
* On Scala 2, all syntax comes from [[Syntax2]] via implicit classes —
* these stubs contribute nothing. On Scala 3 the traits are deliberately
* standalone (no mutual inheritance) so that sibling typeclass givens in
* scope can't present the same inherited extension method via more than one
* member path — see [[Syntax2]]'s Scala-3 docstring.
*/
trait Functor2ExtensionMethods
trait Bifunctor2ExtensionMethods
trait Applicative2ExtensionMethods
trait Guarantee2ExtensionMethods
trait ApplicativeError2ExtensionMethods
trait Monad2ExtensionMethods
trait Error2ExtensionMethods
trait Bracket2ExtensionMethods
trait Panic2ExtensionMethods
trait IO2ExtensionMethods
trait Parallel2ExtensionMethods
trait Concurrent2ExtensionMethods
trait WeakTemporal2ExtensionMethods
trait Temporal2ExtensionMethods
trait Fork2ExtensionMethods

/** Scala 2 stub for the Scala-3-only "InnerF" shadow extension-method trait.
* Empty because Scala 2 routes all syntax through [[Syntax2]]'s implicit
* classes.
*/
trait InnerFExtensionMethodsFromWeakTemporal2
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package izumi.functional.bio.syntax

import izumi.functional.bio.Applicative2

import scala.annotation.targetName

trait Applicative2ExtensionMethods {
extension [F[+_, +_], E, A](r: F[E, A])(using F: Applicative2[F]) {
/** execute two operations in order, return result of second operation */
@targetName("andThenExt")
def *>[E1 >: E, B](f0: => F[E1, B]): F[E1, B] = F.*>(r, f0)

/** execute two operations in order, same as `*>`, but return result of first operation */
@targetName("andKeepExt")
def <*[E1 >: E, B](f0: => F[E1, B]): F[E1, A] = F.<*(r, f0)

/** execute two operations in order, return result of both operations */
@targetName("zipExt")
infix def zip[E2 >: E, B](r2: => F[E2, B]): F[E2, (A, B)] = F.zip(r, r2)

/** execute two operations in order, map their results */
@targetName("map2Ext")
def map2[E2 >: E, B, C](r2: => F[E2, B])(f: (A, B) => C): F[E2, C] = F.map2(r, r2)(f)

@targetName("foreverExt")
def forever: F[E, Nothing] = F.forever(r)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package izumi.functional.bio.syntax

import izumi.functional.bio.ApplicativeError2

import scala.annotation.targetName

trait ApplicativeError2ExtensionMethods {
extension [F[+_, +_], E, A](r: F[E, A])(using F: ApplicativeError2[F]) {
@targetName("orElseExt")
def orElse[E2, A1 >: A](r2: => F[E2, A1]): F[E2, A1] = F.orElse(r, r2)

@targetName("leftMap2Ext")
def leftMap2[E2, A1 >: A, E3](r2: => F[E2, A1])(f: (E, E2) => E3): F[E3, A1] = F.leftMap2(r, r2)(f)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package izumi.functional.bio.syntax

import izumi.functional.bio.Bifunctor2

import scala.annotation.{targetName, unused}

trait Bifunctor2ExtensionMethods {
extension [F[+_, +_], E, A](r: F[E, A])(using F: Bifunctor2[F]) {
@targetName("leftMapBifunctorExt")
def leftMap[E2](f: E => E2): F[E2, A] = F.leftMap(r)(f)

@targetName("bimapBifunctorExt")
def bimap[E2, B](f: E => E2, g: A => B): F[E2, B] = F.bimap(r)(f, g)

@targetName("widenErrorBifunctorExt")
def widenError[E1](using @unused ev: E <:< E1): F[E1, A] = r.asInstanceOf[F[E1, A]]

@targetName("widenBothBifunctorExt")
def widenBoth[E1, A1](using @unused ev1: E <:< E1, @unused ev2: A <:< A1): F[E1, A1] = r.asInstanceOf[F[E1, A1]]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package izumi.functional.bio.syntax

import izumi.functional.bio.{Bracket2, Exit}

import scala.annotation.targetName

trait Bracket2ExtensionMethods {
extension [F[+_, +_], E, A](r: F[E, A])(using F: Bracket2[F]) {
@targetName("bracketExt")
def bracket[E1 >: E, B](release: A => F[Nothing, Unit])(use: A => F[E1, B]): F[E1, B] =
F.bracket(r.asInstanceOf[F[E1, A]])(release)(use)

@targetName("bracketCaseExt")
def bracketCase[E1 >: E, B](release: (A, Exit[E1, B]) => F[Nothing, Unit])(use: A => F[E1, B]): F[E1, B] =
F.bracketCase(r.asInstanceOf[F[E1, A]])(release)(use)

@targetName("guaranteeCaseExt")
def guaranteeCase(cleanup: Exit[E, A] => F[Nothing, Unit]): F[E, A] =
F.guaranteeCase(r, cleanup)

@targetName("bracketOnFailureExt")
def bracketOnFailure[E1 >: E, B](cleanupOnFailure: (A, Exit.Failure[E1]) => F[Nothing, Unit])(use: A => F[E1, B]): F[E1, B] =
F.bracketOnFailure(r.asInstanceOf[F[E1, A]])(cleanupOnFailure)(use)

@targetName("guaranteeOnFailureExt")
def guaranteeOnFailure(cleanupOnFailure: Exit.Failure[E] => F[Nothing, Unit]): F[E, A] =
F.guaranteeOnFailure(r, cleanupOnFailure)

@targetName("guaranteeOnInterruptExt")
def guaranteeOnInterrupt(cleanupOnInterruption: Exit.Interruption => F[Nothing, Unit]): F[E, A] =
F.guaranteeOnInterrupt(r, cleanupOnInterruption)

@targetName("guaranteeExceptOnInterruptExt")
def guaranteeExceptOnInterrupt(cleanupOnNonInterruption: Exit.Uninterrupted[E, A] => F[Nothing, Unit]): F[E, A] =
F.guaranteeExceptOnInterrupt(r, cleanupOnNonInterruption)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package izumi.functional.bio.syntax

import izumi.functional.bio.{Concurrent2, Exit, Fiber2}

import scala.annotation.targetName

trait Concurrent2ExtensionMethods {
extension [F[+_, +_], E, A](r: F[E, A])(using F: Concurrent2[F]) {
@targetName("raceExt")
infix def race[E1 >: E, A1 >: A](that: F[E1, A1]): F[E1, A1] = F.race(r.asInstanceOf[F[E1, A1]], that)

@targetName("racePairUnsafeExt")
def racePairUnsafe[E1 >: E, A1 >: A](
that: F[E1, A1]
): F[E1, Either[(Exit[E1, A], Fiber2[F, E1, A1]), (Fiber2[F, E1, A], Exit[E1, A1])]] = F.racePairUnsafe(r, that)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package izumi.functional.bio.syntax

import izumi.functional.bio.{Error2, WithFilter}
import izumi.fundamentals.platform.language.SourceFilePositionMaterializer

import scala.annotation.targetName

trait Error2ExtensionMethods {
extension [F[+_, +_], E, A](r: F[E, A])(using F: Error2[F]) {
@targetName("catchAllExt")
def catchAll[E2, A2 >: A](h: E => F[E2, A2]): F[E2, A2] = F.catchAll[E, A2, E2](r)(h)

@targetName("catchSomeExt")
def catchSome[E1 >: E, A2 >: A](h: PartialFunction[E, F[E1, A2]]): F[E1, A2] = F.catchSome[E, A2, E1](r)(h)

@targetName("redeemExt")
def redeem[E2, B](err: E => F[E2, B], succ: A => F[E2, B]): F[E2, B] = F.redeem[E, A, E2, B](r)(err, succ)

@targetName("redeemPureExt")
def redeemPure[B](err: E => B, succ: A => B): F[Nothing, B] = F.redeemPure(r)(err, succ)

@targetName("attemptExt")
def attempt: F[Nothing, Either[E, A]] = F.attempt(r)

@targetName("tapErrorExt")
def tapError[E1 >: E](f: E => F[E1, Unit]): F[E1, A] = F.tapError[E, A, E1](r)(f)

@targetName("leftFlatMapExt")
def leftFlatMap[E2](f: E => F[Nothing, E2]): F[E2, A] = F.leftFlatMap(r)(f)

@targetName("flipExt")
def flip: F[A, E] = F.flip(r)

@targetName("tapBothExt")
def tapBoth[E1 >: E, E2 >: E1](err: E => F[E1, Unit])(succ: A => F[E2, Unit]): F[E2, A] = F.tapBoth[E, A, E2](r)(err, succ)

@targetName("fromEitherErrorExt")
def fromEither[E1 >: E, A1](using ev: A <:< Either[E1, A1]): F[E1, A1] =
F.flatMap[E1, A, A1](r)(a => F.fromEither[E1, A1](ev(a)))

@targetName("fromOptionErrorExt")
def fromOption[E1 >: E, A1](errorOnNone: => E1)(using ev1: A <:< Option[A1]): F[E1, A1] =
F.fromOption(errorOnNone, r.asInstanceOf[F[E1, Option[A1]]])

@targetName("retryWhileExt")
def retryWhile(f: E => Boolean): F[E, A] = F.retryWhile(r)(f)

@targetName("retryWhileFExt")
def retryWhileF(f: E => F[Nothing, Boolean]): F[E, A] = F.retryWhileF(r)(f)

@targetName("retryUntilExt")
def retryUntil(f: E => Boolean): F[E, A] = F.retryUntil(r)(f)

@targetName("retryUntilFExt")
def retryUntilF(f: E => F[Nothing, Boolean]): F[E, A] = F.retryUntilF(r)(f)

/** for-comprehensions sugar:
*
* {{{
* for {
* (1, 2) <- F.pure((2, 1)).widenError[NoSuchElementException]
* } yield ()
* }}}
*
* Use [[widenError]] for pattern matching with non-Throwable errors:
*
* {{{
* val f = for {
* (1, 2) <- F.pure((2, 1)).widenError[Option[Unit]]
* } yield ()
* // f: F[Option[Unit], Unit] = F.fail(Some(())
* }}}
*
* Scala 3 implementation note: unlike the Scala 2 Ops-class variant in
* [[Syntax2]], this extension fixes `E1 = E` (the receiver's declared
* error type) rather than accepting a widened `E1 >: E`. Scala 3's
* inference of a free `E1 >: E` combined with `using WithFilter[E1]`
* produces a *union* LUB (e.g. `String | NoSuchElementException`) because
* of union types + covariant `WithFilter[+E]`, silently changing the
* receiver's declared error type. Forcing `E1 = E` keeps the error type
* the receiver already committed to and resolves `WithFilter[E]` via the
* ordinary priority chain. Callers that want a different error type
* (e.g. `NoSuchElementException` when `E = Nothing`) must use
* [[widenError]] explicitly on the receiver before the for-comprehension.
*/
@targetName("withFilterExt")
def withFilter[A1 >: A](predicate: A => Boolean)(using filter: WithFilter[E], pos: SourceFilePositionMaterializer): F[E, A] =
F.withFilter[E, A](r)(predicate)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package izumi.functional.bio.syntax

import izumi.functional.bio.{Fiber2, Fork2}

import scala.annotation.targetName

trait Fork2ExtensionMethods {
extension [F[+_, +_], E, A](r: F[E, A])(using F: Fork2[F]) {
@targetName("forkExt")
def fork: F[Nothing, Fiber2[F, E, A]] = F.fork(r)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package izumi.functional.bio.syntax

import izumi.functional.bio.Functor2

import scala.annotation.{targetName, unused}

trait Functor2ExtensionMethods {
extension [F[+_, +_], E, A](r: F[E, A])(using F: Functor2[F]) {
@targetName("mapExt")
def map[B](f: A => B): F[E, B] = F.map(r)(f)

@targetName("asExt")
infix def as[B](b: => B): F[E, B] = F.map(r)(_ => b)

@targetName("voidExt")
def void: F[E, Unit] = F.void(r)

@targetName("widenExt")
def widen[A1](using @unused ev: A <:< A1): F[E, A1] = r.asInstanceOf[F[E, A1]]

@targetName("fromOptionOrExt")
def fromOptionOr[B, C](valueOnNone: => C)(using @unused ev: A <:< Option[B], ev2: C <:< B): F[E, B] =
F.fromOptionOr(ev2(valueOnNone), r.asInstanceOf[F[E, Option[B]]])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package izumi.functional.bio.syntax

import izumi.functional.bio.Guarantee2

import scala.annotation.targetName

trait Guarantee2ExtensionMethods {
extension [F[+_, +_], E, A](r: F[E, A])(using F: Guarantee2[F]) {
@targetName("guaranteeExt")
def guarantee(cleanup: F[Nothing, Unit]): F[E, A] = F.guarantee(r, cleanup)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package izumi.functional.bio.syntax

import izumi.functional.bio.IO2

import scala.annotation.{targetName, unused}

trait IO2ExtensionMethods {
extension [F[+_, +_], E, A](r: F[E, A])(using F: IO2[F]) {
@targetName("bracketAutoExt")
def bracketAuto[E1 >: E, B](use: A => F[E1, B])(using @unused ev: A <:< AutoCloseable): F[E1, B] =
F.bracket[E1, A, B](r.asInstanceOf[F[E1, A]])(c => F.sync(ev(c).close()))(use)
}
}
Loading
Loading