com.thoughtworks.dsl.domains

Type members

Classlikes

object Task extends TaskPlatformSpecificFunctions
Authors

杨博 (Yang Bo)

object scalaz

Contains interpreters to enable !-notation for Monadic and other keywords in code blocks whose type support scalaz.Bind, scalaz.MonadError and scalaz.MonadTrans.

Contains interpreters to enable !-notation for Monadic and other keywords in code blocks whose type support scalaz.Bind, scalaz.MonadError and scalaz.MonadTrans.

Authors

杨博 (Yang Bo)

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 !-notation, similar to the each method in 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.bangnotation._
        val trampoline3 = Trampoline.done(3)
        def dslSquare = reset(Trampoline.delay {
          s"This string is produced by a trampoline: ${!Monadic(trampoline3) * !Monadic(trampoline3)}"
        })
        dslSquare.run should be("This string is produced by a trampoline: 9")
    `!trampoline3` is a shortcut of `!Monadic(trampoline3)`,
    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: ${!Monadic(trampoline3) / !Monadic(trampolineSuccess0)}"
          } catch {
            case e: ArithmeticException =>
              s"Cannot divide ${!Monadic(trampoline3)} by ${!Monadic(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 =>
                   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"))
        }

Types

type Continuation[R, +A] = A => R => R
type Task[+A] = TaskDomain => A

The asynchronous task that supports exception handling, resource management, and is stack-safe.

The asynchronous task that supports exception handling, resource management, and is stack-safe.

Example

A Task can be created from for-comprehension, where keywords.Each and keywords.Fork can be used together to asynchronously iterate collections. For example, the above concatenateRemoteData downloads and concatenates data from multiple URLs.

        import com.thoughtworks.dsl.bangnotation._
        import com.thoughtworks.dsl._
        import com.thoughtworks.dsl.keywords._
        import com.thoughtworks.dsl.keywords.Shift._
        import com.thoughtworks.dsl.domains.Task
        import java.net.URL
        def concatenateRemoteData(urls: List[URL], downloader: URL => Task[Vector[Byte]]): Task[Vector[Byte]] = {
          for {
            url <- Fork(urls)
            data <- Shift(downloader(url))
            byte <- Each(data)
          } yield byte
        }.as[Task[Vector[Byte]]]
    A [[Task]] can be also created from [[Task.apply]]
        def mockDownloader(url: URL) = Task {
          "mock data\n".getBytes.toVector
        }
    A [[Task]] can be then converted to [[scala.concurrent.Future]] via [[Task.toFuture]],
    in order to integrate into other frameworks.
    In this example, it's a `Future[Assertion]` required by [[org.scalatest.freespec.AsyncFreeSpec]].
        val mockUrls = List(new URL("http://example.com/file1"), new URL("http://example.com/file2"))
        import org.scalatest.Assertion
        def assertion: Task[Assertion] = *[Task] {
          !Shift(concatenateRemoteData(mockUrls, mockDownloader)) should be("mock data\nmock data\n".getBytes.toVector)
        }
        Task.toFuture(assertion)