Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove ScalazTransformerDsl in favor of Lift.OneStep #560

Merged
merged 1 commit into from
Dec 22, 2021
Merged
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
337 changes: 175 additions & 162 deletions domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,121 +12,126 @@ import com.thoughtworks.dsl.Dsl.{TryCatch, TryFinally}
import scala.util.control.Exception.Catcher
import scala.util.control.NonFatal

/** Contains interpreters to enable [[Dsl.Keyword#unary_$bang !-notation]]
* for [[keywords.Monadic Monadic]] and other keywords
* in code blocks whose type support [[scalaz.Bind]], [[scalaz.MonadError]] and [[scalaz.MonadTrans]].
*
* @example [[scalaz.Free.Trampoline]] is a monadic data type that performs tail call optimization.
* It can be built from a `@[[Dsl.reset reset]]` code block within some [[Dsl.Keyword#unary_$bang !-notation]],
* similar to the [[com.thoughtworks.each.Monadic.EachOps#each each]] method in
* [[https://github.com/ThoughtWorksInc/each ThoughtWorks Each]].
*
* {{{
* import _root_.scalaz.Trampoline
* import _root_.scalaz.Free.Trampoline
* import com.thoughtworks.dsl.keywords.Monadic
* import com.thoughtworks.dsl.domains.scalaz.given
* import com.thoughtworks.dsl.reset
* import com.thoughtworks.dsl.keywords.Monadic.unary_!
*
* val trampoline3 = Trampoline.done(3)
*
* def dslSquare = reset(Trampoline.delay {
* s"This string is produced by a trampoline: ${!trampoline3 * !trampoline3}"
* })
*
* dslSquare.run should be("This string is produced by a trampoline: 9")
* }}}
*
* `!trampoline3` is a shortcut of `!Monadic(trampoline3)`,
* enabled by `import com.thoughtworks.dsl.keywords.Monadic.given`,
* which will be converted to `flatMap` calls by our DSL interpreter.
* Thus, the method `dslSquare` is equivalent to the following code in [[scalaz.syntax]]:
*
* {{{
*
* def scalazSyntaxSquare = trampoline3.flatMap { tmp1 =>
* trampoline3.flatMap { tmp2 =>
* Trampoline.delay {
* s"This string is produced by a trampoline: ${tmp1 * tmp2}"
* }
* }
* }
*
* scalazSyntaxSquare.run should be("This string is produced by a trampoline: 9")
* }}}
*
* <hr/>
*
* A `@[[Dsl.reset reset]]` code block can contain `try` / `catch` / `finally`
* if the monadic data type supports [[scalaz.MonadError]].
*
* [[https://github.com/ThoughtWorksInc/tryt.scala tryt.scala]] is a monad transformer that provides
* [[scalaz.MonadError]],
* therefore `try` / `catch` / `finally` expressions can be used inside a `@[[Dsl.reset reset]]` code block
* whose return type is `TryT[Trampoline, ?]`.
*
* {{{
* import com.thoughtworks.tryt.invariant.TryT, TryT.given
* import scala.util.{Try, Success}
* type TryTTransfomredTrampoline[A] = TryT[Trampoline, A]
*
* val trampolineSuccess0: TryTTransfomredTrampoline[Int] = TryT(Trampoline.done(Try(0)))
*
* def dslTryCatch: TryTTransfomredTrampoline[String] = reset(TryT(Trampoline.delay(Try {
* try {
* s"Division result: ${!trampoline3 / !trampolineSuccess0}"
* } catch {
* case e: ArithmeticException =>
* s"Cannot divide ${!trampoline3} by ${!trampolineSuccess0}"
* }
* })))
*
* inside(dslTryCatch) {
* case TryT(trampoline) =>
* trampoline.run should be(Success("Cannot divide 3 by 0"))
* }
* }}}
*
* Note that [[Dsl.Keyword#unary_$bang !-notation]] can be used on
* both `trampoline3` and `trampolineSuccess0` even when they are different types,
* i.e. `trampoline3` is a vanilla [[scalaz.Free.Trampoline Trampoline]],
* while `trampolineSuccess0` is a [[com.thoughtworks.tryt.invariant.TryT TryT]]-transfomred
* [[scalaz.Free.Trampoline Trampoline]].
* It is possible because the interpreters of the [[keywords.Monadic]] invoke
* [[scalaz.MonadTrans.liftM]] automatically.
*
* The above `dslTryCatch` method is equivalent to the following code in [[scalaz.syntax]]:
*
* {{{
* import _root_.scalaz.syntax.monad._
* def scalazSyntaxTryCatch: TryTTransfomredTrampoline[String] = {
* import _root_.scalaz.syntax.monadError._
* trampoline3.liftM[TryT].flatMap { tmp0 =>
* trampolineSuccess0.flatMap { tmp1 =>
/** Contains interpreters to enable [[Dsl.Keyword#unary_$bang !-notation]] for
* [[keywords.Monadic Monadic]] and other keywords in code blocks whose type
* support [[scalaz.Bind]], [[scalaz.MonadError]] and [[scalaz.MonadTrans]].
*
* @example
* [[scalaz.Free.Trampoline]] is a monadic data type that performs tail call
* optimization. It can be built from a `@[[Dsl.reset reset]]` code block
* within some [[Dsl.Keyword#unary_$bang !-notation]], similar to the
* [[com.thoughtworks.each.Monadic.EachOps#each each]] method in
* [[https://github.com/ThoughtWorksInc/each ThoughtWorks Each]].
*
* {{{
* import _root_.scalaz.Trampoline
* import _root_.scalaz.Free.Trampoline
* import com.thoughtworks.dsl.keywords.Monadic
* import com.thoughtworks.dsl.domains.scalaz.given
* import com.thoughtworks.dsl.reset
* import com.thoughtworks.dsl.keywords.Monadic.unary_!
*
* val trampoline3 = Trampoline.done(3)
*
* def dslSquare = reset(Trampoline.delay {
* s"This string is produced by a trampoline: ${!trampoline3 * !trampoline3}"
* })
*
* dslSquare.run should be("This string is produced by a trampoline: 9")
* }}}
*
* `!trampoline3` is a shortcut of `!Monadic(trampoline3)`, enabled by `import
* com.thoughtworks.dsl.keywords.Monadic.given`, which will be converted to
* `flatMap` calls by our DSL interpreter. Thus, the method `dslSquare` is
* equivalent to the following code in [[scalaz.syntax]]:
*
* {{{
*
* def scalazSyntaxSquare = trampoline3.flatMap { tmp1 =>
* trampoline3.flatMap { tmp2 =>
* Trampoline.delay {
* s"This string is produced by a trampoline: ${tmp1 * tmp2}"
* }
* }
* }
*
* scalazSyntaxSquare.run should be("This string is produced by a trampoline: 9")
* }}}
*
* <hr/>
*
* A `@[[Dsl.reset reset]]` code block can contain `try` / `catch` / `finally`
* if the monadic data type supports [[scalaz.MonadError]].
*
* [[https://github.com/ThoughtWorksInc/tryt.scala tryt.scala]] is a monad
* transformer that provides [[scalaz.MonadError]], therefore `try` / `catch` /
* `finally` expressions can be used inside a `@[[Dsl.reset reset]]` code block
* whose return type is `TryT[Trampoline, ?]`.
*
* {{{
* import com.thoughtworks.tryt.invariant.TryT, TryT.given
* import scala.util.{Try, Success}
* type TryTTransfomredTrampoline[A] = TryT[Trampoline, A]
*
* val trampolineSuccess0: TryTTransfomredTrampoline[Int] = TryT(Trampoline.done(Try(0)))
*
* def dslTryCatch: TryTTransfomredTrampoline[String] = reset(TryT(Trampoline.delay(Try {
* try {
* s"Division result: ${!trampoline3 / !trampolineSuccess0}"
* } catch {
* case e: ArithmeticException =>
* s"Cannot divide ${!trampoline3} by ${!trampolineSuccess0}"
* }
* })))
*
* inside(dslTryCatch) {
* case TryT(trampoline) =>
* trampoline.run should be(Success("Cannot divide 3 by 0"))
* }
* }}}
*
* Note that [[Dsl.Keyword#unary_$bang !-notation]] can be used on both
* `trampoline3` and `trampolineSuccess0` even when they are different types,
* i.e. `trampoline3` is a vanilla [[scalaz.Free.Trampoline Trampoline]], while
* `trampolineSuccess0` is a
* [[com.thoughtworks.tryt.invariant.TryT TryT]]-transfomred
* [[scalaz.Free.Trampoline Trampoline]]. It is possible because the
* interpreters of the [[keywords.Monadic]] invoke [[scalaz.MonadTrans.liftM]]
* automatically.
*
* The above `dslTryCatch` method is equivalent to the following code in
* [[scalaz.syntax]]:
*
* {{{
* import _root_.scalaz.syntax.monad._
* def scalazSyntaxTryCatch: TryTTransfomredTrampoline[String] = {
* import _root_.scalaz.syntax.monadError._
* trampoline3.liftM[TryT].flatMap { tmp0 =>
* trampolineSuccess0.flatMap { tmp1 =>
* TryT(Trampoline.delay(Try(s"Division result: ${tmp0 / tmp1}")))
* }
* }.handleError {
* case e: ArithmeticException =>
* trampoline3.liftM[TryT].flatMap { tmp2 =>
* trampolineSuccess0.flatMap { tmp3 =>
* }
* }.handleError {
* case e: ArithmeticException =>
* trampoline3.liftM[TryT].flatMap { tmp2 =>
* trampolineSuccess0.flatMap { tmp3 =>
* TryT(Trampoline.delay(Try(s"Cannot divide ${tmp2} by ${tmp3}")))
* }
* }
* case e =>
* e.raiseError[TryTTransfomredTrampoline, String]
* }
* }
*
* inside(scalazSyntaxTryCatch) {
* case TryT(trampoline) =>
* trampoline.run should be(Success("Cannot divide 3 by 0"))
* }
* }}}
*
* @author 杨博 (Yang Bo)
* }
* }
* case e =>
* e.raiseError[TryTTransfomredTrampoline, String]
* }
* }
*
* inside(scalazSyntaxTryCatch) {
* case TryT(trampoline) =>
* trampoline.run should be(Success("Cannot divide 3 by 0"))
* }
* }}}
*
* @author
* 杨博 (Yang Bo)
*/
object scalaz {
object scalaz extends scalaz.LowPriority0 {

protected type MonadThrowable[F[_]] = MonadError[F, Throwable]

Expand All @@ -145,15 +150,20 @@ object scalaz {
monadError: MonadThrowable[F]
): TryFinally[A, F[B], F[A], F[Unit]] =
new TryFinally[A, F[B], F[A], F[Unit]] {
def tryFinally(block: F[A] !! A, finalizer: F[Unit] !! Unit, outerSuccessHandler: A => F[B]): F[B] = {
def tryFinally(
block: F[A] !! A,
finalizer: F[Unit] !! Unit,
outerSuccessHandler: A => F[B]
): F[B] = {
@inline
def injectFinalizer[A](f: Unit => F[A]): F[A] = {
monadError.bind(catchNativeException(finalizer))(f)
}
monadError.bind(monadError.handleError(catchNativeException(block)) { (e: Throwable) =>
injectFinalizer { (_: Unit) =>
monadError.raiseError(e)
}
monadError.bind(monadError.handleError(catchNativeException(block)) {
(e: Throwable) =>
injectFinalizer { (_: Unit) =>
monadError.raiseError(e)
}
}) { a =>
injectFinalizer { (_: Unit) =>
outerSuccessHandler(a)
Expand All @@ -162,9 +172,15 @@ object scalaz {
}
}

implicit def scalazTryCatch[F[_], A, B](implicit monadError: MonadThrowable[F]): TryCatch[A, F[B], F[A]] =
implicit def scalazTryCatch[F[_], A, B](implicit
monadError: MonadThrowable[F]
): TryCatch[A, F[B], F[A]] =
new TryCatch[A, F[B], F[A]] {
def tryCatch(block: F[A] !! A, catcher: Catcher[F[A] !! A], outerSuccessHandler: A => F[B]): F[B] = {
def tryCatch(
block: F[A] !! A,
catcher: Catcher[F[A] !! A],
outerSuccessHandler: A => F[B]
): F[B] = {
import monadError.monadErrorSyntax._
catchNativeException(block)
.handleError { e =>
Expand All @@ -186,53 +202,50 @@ object scalaz {
.flatMap(outerSuccessHandler)
}
}

implicit def scalazLift[F[_], A, B](implicit
applicative: Applicative[F],
): Dsl.Lift.OneStep[A, F[A]] =
applicative.pure

given [F[_[_], _], H[_], G[_], A, B](using
monadTrans: MonadTrans[F],
rest: ScalazTransformerDsl[H, G, A, B]
): Dsl.Atomic[Monadic[H[A]], F[G, B], A] =
Dsl.Atomic(new ScalazTransformerDsl[H, [X] =>> F[G, X], A, B] {

def monad: Monad[[X] =>> F[G, X]] = monadTrans(rest.monad)

def lift(fa: H[A]): F[G, A] = monadTrans.liftM(rest.lift(fa))(rest.monad)

})
private[scalaz] trait LowPriority0:
/** The [[Dsl]] instance that converts a keyword to the monad domain type
* then flatMap. This instance helps when the keyword supports a domain `D`
* that can be lifted to the `F[A]`, while there is not general rule to
* derive `F[A]` from `D`. For example, when `F[A]` is a monad transformer
* and `D` is the underlying monad type.
*/
given [F[_], K, A, G[_], B](using
monad: Monad[F],
lift: Dsl.Lift[G[A], F[A]],
dsl: Dsl.Searching[K, G[A], A],
liftG: Dsl.Lift[A, G[A]]
): Dsl.Derived.StackUnsafe[K, F[B], A] =
Dsl.Derived.StackUnsafe { (keyword: K, handler: A => F[B]) =>
monad.bind(lift(dsl(keyword, liftG)))(
handler
)
}
given [F[_], A, B](using
applicative: Applicative[F]
): Dsl.Lift.OneStep[A, F[A]] =
applicative.pure

given [F[_[_], _], G[_], A, B](using
monadTrans: MonadTrans[F],
monad0: Monad[G]
): Dsl.Atomic[Monadic[G[A]], F[G, B], A] =
Dsl.Atomic(new ScalazTransformerDsl[G, [X] =>> F[G, X], A, B] {
def monad = monadTrans(monad0)

def lift(fa: G[A]): F[G, A] = monadTrans.liftM(fa)

})

given [F[_], A, B](using
bind: Bind[F]
): Dsl.Atomic[Monadic[F[A]], F[B], A] =
Dsl.Atomic[Monadic[F[A]], F[B], A] {
(keyword: Monadic[F[A]], handler: A => F[B]) =>
bind.bind(Monadic.apply.flip(keyword))(handler)
}

abstract class ScalazTransformerDsl[F[_], G[_], A, B]
extends ((Monadic[F[A]], A => G[B]) => G[B]) {
def monad: Monad[G]

def lift(fa: F[A]): G[A]
monad: Monad[G]
): Dsl.Lift.OneStep[G[A], F[G, A]] = {
monadTrans.liftM(_)
}

final def apply(keyword: Monadic[F[A]], handler: A => G[B]): G[B] = {
monad.bind(lift(Monadic.apply.flip(keyword)))(handler)
/** The [[Dsl]] instance that converts a [[domains.Monadic]] keyword to the
* monad domain type then flatMap. This instance helps when the keyword
* supports a domain `D` that can be lifted to the `F[A]`, while there is not
* general rule to derive `F[A]` from `D`. For example, when `F[A]` is a
* monad transformer and `D` is the underlying monad type.
*/
given [F[_], A, G[_], B](using
monad: Bind[F],
lift: Dsl.Lift[G[A], F[A]]
): Dsl.Atomic[Monadic[G[A]], F[B], A] =
Dsl.Atomic { (keyword: Monadic[G[A]], handler: A => F[B]) =>
monad.bind(lift(Monadic.apply.flip(keyword)))(
handler
)
}

}

}