diff --git a/.scalafmt.conf b/.scalafmt.conf
index 08ecba657..031c0455c 100644
--- a/.scalafmt.conf
+++ b/.scalafmt.conf
@@ -1,3 +1,3 @@
runner.dialect = "scala213"
-version = "3.2.1"
+version = "3.1.2"
maxColumn = 120
diff --git a/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala b/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala
index c9db95b9c..8c7c185b1 100644
--- a/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala
+++ b/Dsl/src/main/scala/com/thoughtworks/dsl/Dsl.scala
@@ -13,20 +13,17 @@ import scala.util.{Failure, Success, Try}
import scala.util.control.{NonFatal, TailCalls}
import scala.util.control.TailCalls.TailRec
-/** The domain-specific interpreter for `Keyword` in `Domain`, which is a dependent type type class that registers an
- * asynchronous callback function, to handle the `Value` inside `Keyword`.
+/** The domain-specific interpreter for `Keyword` in `Domain`,
+ * which is a dependent type type class that registers an asynchronous callback function,
+ * to handle the `Value` inside `Keyword`.
*
- * @tparam Value
- * The value held inside `Keyword`.
- * @author
- * 杨博 (Yang Bo)
- * @example
- * Creating a collaborative DSL in [[https://github.com/ThoughtWorksInc/Dsl.scala Dsl.scala]] is easy. Only two steps
- * are required:
+ * @tparam Value The value held inside `Keyword`.
+ * @author 杨博 (Yang Bo)
+ * @example Creating a collaborative DSL in [[https://github.com/ThoughtWorksInc/Dsl.scala Dsl.scala]] is easy.
+ * Only two steps are required:
*
- * - Defining their domain-specific [[com.thoughtworks.dsl.Dsl.Keyword Keyword]].
- * - Implementing this [[Dsl]] type class, which is an interpreter for an
- * [[com.thoughtworks.dsl.Dsl.Keyword Keyword]].
+ * - Defining their domain-specific [[com.thoughtworks.dsl.Dsl.Keyword Keyword]].
+ * - Implementing this [[Dsl]] type class, which is an interpreter for an [[com.thoughtworks.dsl.Dsl.Keyword Keyword]].
*/
@implicitNotFound("The keyword ${Keyword} is not supported inside a function that returns ${Domain}.")
trait Dsl[-Keyword, Domain, +Value] {
@@ -239,9 +236,8 @@ object Dsl extends LowPriorityDsl0 {
/** An annotation to explicitly perform reset control operator on a code block.
*
- * @note
- * This annotation can be automatically added if [[compilerplugins.ResetEverywhere ResetEverywhere]] compiler
- * plug-in is enabled.
+ * @note This annotation can be automatically added
+ * if [[compilerplugins.ResetEverywhere ResetEverywhere]] compiler plug-in is enabled.
*/
final class reset extends ResetAnnotation with StaticAnnotation with TypeConstraint
@@ -251,11 +247,9 @@ object Dsl extends LowPriorityDsl0 {
def apply[Keyword, Domain, Value](implicit typeClass: Dsl[Keyword, Domain, Value]): Dsl[Keyword, Domain, Value] =
typeClass
- /** @tparam Self
- * the self type
- * @see
- * [[https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern Curiously recurring template pattern]] for
- * the reason why we need the `Self` type parameter
+ /** @tparam Self the self type
+ * @see [[https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern Curiously recurring template pattern]]
+ * for the reason why we need the `Self` type parameter
*/
trait Keyword[Self, Value] extends Any { this: Self =>
@@ -293,8 +287,8 @@ object Dsl extends LowPriorityDsl0 {
/** The type class to support `try` ... `catch` ... `finally` expression for `OutputDomain`.
*
- * !-notation is allowed by default for `? !! Throwable` and [[scala.concurrent.Future Future]] domains, with the
- * help of this type class.
+ * !-notation is allowed by default for `? !! Throwable` and [[scala.concurrent.Future Future]] domains,
+ * with the help of this type class.
*/
@implicitNotFound(
"The `try` ... `catch` ... `finally` expression cannot contain !-notation inside a function that returns ${OuterDomain}."
diff --git a/compilerplugins-BangNotation/src/main/scala/com/thoughtworks/dsl/compilerplugins/BangNotation.scala b/compilerplugins-BangNotation/src/main/scala/com/thoughtworks/dsl/compilerplugins/BangNotation.scala
index b6b8816db..07f2a1db4 100644
--- a/compilerplugins-BangNotation/src/main/scala/com/thoughtworks/dsl/compilerplugins/BangNotation.scala
+++ b/compilerplugins-BangNotation/src/main/scala/com/thoughtworks/dsl/compilerplugins/BangNotation.scala
@@ -16,16 +16,17 @@ private object BangNotation {
}
-/** The Scala compiler plug-in to convert ordinary Scala control flows to continuation-passing style, which will then be
- * interpreted by [[Dsl]].
+/** The Scala compiler plug-in to convert ordinary Scala control flows to continuation-passing style,
+ * which will then be interpreted by [[Dsl]].
*
- * =Usage=
+ * = Usage =
*
- * `
// In your build.sbt addCompilerPlugin("com.thoughtworks.dsl" %% "compilerplugins-bangnotation" %
- * "latest.release") `
+ * `
+ * // In your build.sbt
+ * addCompilerPlugin("com.thoughtworks.dsl" %% "compilerplugins-bangnotation" % "latest.release")
+ * `
*
- * @author
- * 杨博 (Yang Bo)
+ * @author 杨博 (Yang Bo)
*/
final class BangNotation(override val global: Global) extends Plugin {
import global._
@@ -64,8 +65,7 @@ final class BangNotation(override val global: Global) extends Plugin {
/** Avoid [[UnApply]] in `tree` to suppress compiler crash due to `unexpected UnApply xxx`.
*
- * @see
- * https://github.com/scala/bug/issues/8825
+ * @see https://github.com/scala/bug/issues/8825
*/
private def scalaBug8825Workaround(tree: Tree): Tree = {
val transformer = new Transformer {
diff --git a/compilerplugins-BangNotation/src/test/scala/com/thoughtworks/dsl/compilerplugin/BangNotationSpec.scala b/compilerplugins-BangNotation/src/test/scala/com/thoughtworks/dsl/compilerplugin/BangNotationSpec.scala
index ea3fa036d..55c18ff26 100644
--- a/compilerplugins-BangNotation/src/test/scala/com/thoughtworks/dsl/compilerplugin/BangNotationSpec.scala
+++ b/compilerplugins-BangNotation/src/test/scala/com/thoughtworks/dsl/compilerplugin/BangNotationSpec.scala
@@ -4,8 +4,7 @@ import com.thoughtworks.dsl.Dsl.shift
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
class BangNotationSpec extends AnyFreeSpec with Matchers {
diff --git a/compilerplugins-ResetEverywhere/src/main/scala/com/thoughtworks/dsl/compilerplugins/ResetEverywhere.scala b/compilerplugins-ResetEverywhere/src/main/scala/com/thoughtworks/dsl/compilerplugins/ResetEverywhere.scala
index b796da982..d333139bc 100644
--- a/compilerplugins-ResetEverywhere/src/main/scala/com/thoughtworks/dsl/compilerplugins/ResetEverywhere.scala
+++ b/compilerplugins-ResetEverywhere/src/main/scala/com/thoughtworks/dsl/compilerplugins/ResetEverywhere.scala
@@ -12,16 +12,16 @@ import scala.tools.nsc.{Global, Mode, Phase}
*
* Add the following setting in your `build.sbt` to enable this plug-in.
*
- * ` // build.sbt addCompilerPlugin("com.thoughtworks.dsl" %% "compilerplugins-reseteverywhere" %
- * "latest.release") `
+ * `
+ * // build.sbt
+ * addCompilerPlugin("com.thoughtworks.dsl" %% "compilerplugins-reseteverywhere" % "latest.release")
+ * `
*
- * @note
- * Once this [[ResetEverywhere]] plug-in is enabled, the `@[[Dsl.reset reset]]` annotations are added to class
- * fields, every methods and every functions automatically. Some other macros or compiler plug-ins may conflict with
- * those `@[[Dsl.reset reset]]` annotations.
+ * @note Once this [[ResetEverywhere]] plug-in is enabled,
+ * the `@[[Dsl.reset reset]]` annotations are added to class fields, every methods and every functions automatically.
+ * Some other macros or compiler plug-ins may conflict with those `@[[Dsl.reset reset]]` annotations.
*
- * @author
- * 杨博 (Yang Bo)
+ * @author 杨博 (Yang Bo)
*/
final class ResetEverywhere(override val global: Global) extends Plugin {
import global._
diff --git a/comprehension/src/main/scala/com/thoughtworks/dsl/comprehension.scala b/comprehension/src/main/scala/com/thoughtworks/dsl/comprehension.scala
index 7d2285cc6..44f0b5037 100644
--- a/comprehension/src/main/scala/com/thoughtworks/dsl/comprehension.scala
+++ b/comprehension/src/main/scala/com/thoughtworks/dsl/comprehension.scala
@@ -21,104 +21,99 @@ private[dsl] sealed trait LowPriorityComprehension0 {
* import com.thoughtworks.dsl.comprehension._
* }}}
*
- * @example
- * `for` / `yield` expressions can be used on keywords.
- *
- * {{{
- * import com.thoughtworks.dsl.keywords._
- *
- * def cartesianProduct = for {
- * i <- Each(Array(1, 2, 3))
- * j <- Each(Vector(1, 10, 100, 1000))
- * } yield i * j
- * }}}
- *
- * The results of `for` / `yield` expressions are also keywords.
- *
- * {{{
- * cartesianProduct should be(a[Dsl.Keyword[_, _]])
- * }}}
- *
- * You can use !-notation extract the value from the produced keyword.
- *
- * {{{
- * def resultAsList = List(!cartesianProduct)
- * resultAsList should be(List(1, 10, 100, 1000, 2, 20, 200, 2000, 3, 30, 300, 3000))
- *
- * def resultAsSet: Set[Int] = !Return(!cartesianProduct)
- * resultAsSet should be(Set(1, 10, 100, 1000, 2, 20, 200, 2000, 3, 30, 300, 3000))
- * }}}
- *
- * Alternatively, [[comprehension.ComprehensionOps.to]] can be used to convert the result of a keyword to other types
- * of values as well.
- *
- * {{{
- * cartesianProduct.to[List] should be(List(1, 10, 100, 1000, 2, 20, 200, 2000, 3, 30, 300, 3000))
- * }}}
- * @example
- * This example implements the same feature as the example on Scaladoc of [[keywords.Yield]], except this example use
- * `for`-comprehension instead of !-notation.
- *
- * {{{
- * import com.thoughtworks.dsl.Dsl
- * import com.thoughtworks.dsl.keywords._
- * import com.thoughtworks.dsl.comprehension._
- *
- * def gccFlagBuilder(sourceFile: String, includes: String*) = {
- * for {
- * _ <- Yield("gcc")
- * _ <- Yield("-c")
- * _ <- Yield(sourceFile)
- * include <- Each(includes)
- * _ <- Yield("-I")
- * _ <- Yield(include)
- * r <- Continue
- * } yield r: String
- * }
- *
- * gccFlagBuilder("main.c", "lib1/include", "lib2/include").to[Stream] should be(Stream("gcc", "-c", "main.c", "-I", "lib1/include", "-I", "lib2/include"))
- * }}}
- *
- * Alternatively, you can use Scala native `yield` keyword to produce the last value.
- *
- * {{{
- * def gccFlagBuilder2(sourceFile: String, includes: String*) = {
- * for {
- * _ <- Yield("gcc")
- * _ <- Yield("-c")
- * _ <- Yield(sourceFile)
- * include <- Each(includes)
- * _ <- Yield("-I")
- * } yield include
- * }
- * gccFlagBuilder2("main.c", "lib1/include", "lib2/include").to[Stream] should be(Stream("gcc", "-c", "main.c", "-I", "lib1/include", "-I", "lib2/include"))
- * }}}
- *
- * You can also omit the explicit constructor of [[keywords.Yield]] with the help of implicit conversion
- * [[keywords.Yield.implicitYield]].
- *
- * {{{
- * import com.thoughtworks.dsl.keywords.Yield.implicitYield
- *
- * def augmentString = ()
- * def wrapString = ()
- * }}}
- *
- * Note that [[scala.Predef.augmentString]] and [[scala.Predef.wrapString]] must be disabled in order to use `flatMap`
- * for [[keywords.Yield]].
- *
- * {{{
- * def gccFlagBuilder3(sourceFile: String, includes: String*) = {
- * for {
- * _ <- "gcc"
- * _ <- "-c"
- * _ <- sourceFile
- * include <- Each(includes)
- * _ <- "-I"
- * } yield include
- * }
- * gccFlagBuilder3("main.c", "lib1/include", "lib2/include").to[Stream] should be(Stream("gcc", "-c", "main.c", "-I", "lib1/include", "-I", "lib2/include"))
- * }}}
+ * @example `for` / `yield` expressions can be used on keywords.
+ *
+ * {{{
+ * import com.thoughtworks.dsl.keywords._
+ *
+ * def cartesianProduct = for {
+ * i <- Each(Array(1, 2, 3))
+ * j <- Each(Vector(1, 10, 100, 1000))
+ * } yield i * j
+ * }}}
+ *
+ * The results of `for` / `yield` expressions are also keywords.
+ *
+ * {{{
+ * cartesianProduct should be(a[Dsl.Keyword[_, _]])
+ * }}}
+ *
+ * You can use !-notation extract the value from the produced keyword.
+ *
+ * {{{
+ * def resultAsList = List(!cartesianProduct)
+ * resultAsList should be(List(1, 10, 100, 1000, 2, 20, 200, 2000, 3, 30, 300, 3000))
+ *
+ * def resultAsSet: Set[Int] = !Return(!cartesianProduct)
+ * resultAsSet should be(Set(1, 10, 100, 1000, 2, 20, 200, 2000, 3, 30, 300, 3000))
+ * }}}
+ *
+ * Alternatively, [[comprehension.ComprehensionOps.to]] can be used to convert the result of a keyword to other types of values as well.
+ *
+ * {{{
+ * cartesianProduct.to[List] should be(List(1, 10, 100, 1000, 2, 20, 200, 2000, 3, 30, 300, 3000))
+ * }}}
+ * @example This example implements the same feature as the example on Scaladoc of [[keywords.Yield]],
+ * except this example use `for`-comprehension instead of !-notation.
+ *
+ * {{{
+ * import com.thoughtworks.dsl.Dsl
+ * import com.thoughtworks.dsl.keywords._
+ * import com.thoughtworks.dsl.comprehension._
+ *
+ * def gccFlagBuilder(sourceFile: String, includes: String*) = {
+ * for {
+ * _ <- Yield("gcc")
+ * _ <- Yield("-c")
+ * _ <- Yield(sourceFile)
+ * include <- Each(includes)
+ * _ <- Yield("-I")
+ * _ <- Yield(include)
+ * r <- Continue
+ * } yield r: String
+ * }
+ *
+ * gccFlagBuilder("main.c", "lib1/include", "lib2/include").to[Stream] should be(Stream("gcc", "-c", "main.c", "-I", "lib1/include", "-I", "lib2/include"))
+ * }}}
+ *
+ * Alternatively, you can use Scala native `yield` keyword to produce the last value.
+ *
+ * {{{
+ * def gccFlagBuilder2(sourceFile: String, includes: String*) = {
+ * for {
+ * _ <- Yield("gcc")
+ * _ <- Yield("-c")
+ * _ <- Yield(sourceFile)
+ * include <- Each(includes)
+ * _ <- Yield("-I")
+ * } yield include
+ * }
+ * gccFlagBuilder2("main.c", "lib1/include", "lib2/include").to[Stream] should be(Stream("gcc", "-c", "main.c", "-I", "lib1/include", "-I", "lib2/include"))
+ * }}}
+ *
+ * You can also omit the explicit constructor of [[keywords.Yield]] with the help of implicit conversion [[keywords.Yield.implicitYield]].
+ *
+ * {{{
+ * import com.thoughtworks.dsl.keywords.Yield.implicitYield
+ *
+ * def augmentString = ()
+ * def wrapString = ()
+ * }}}
+ *
+ * Note that [[scala.Predef.augmentString]] and [[scala.Predef.wrapString]] must be disabled in order to use `flatMap` for [[keywords.Yield]].
+ *
+ * {{{
+ * def gccFlagBuilder3(sourceFile: String, includes: String*) = {
+ * for {
+ * _ <- "gcc"
+ * _ <- "-c"
+ * _ <- sourceFile
+ * include <- Each(includes)
+ * _ <- "-I"
+ * } yield include
+ * }
+ * gccFlagBuilder3("main.c", "lib1/include", "lib2/include").to[Stream] should be(Stream("gcc", "-c", "main.c", "-I", "lib1/include", "-I", "lib2/include"))
+ * }}}
*/
object comprehension extends LowPriorityComprehension0 {
diff --git a/domains-scalaz/.jvm/src/test/scala/com/thoughtworks/dsl/domains/scalazSpec.scala b/domains-scalaz/.jvm/src/test/scala/com/thoughtworks/dsl/domains/scalazSpec.scala
index 7753db4a0..09d7f0aaa 100644
--- a/domains-scalaz/.jvm/src/test/scala/com/thoughtworks/dsl/domains/scalazSpec.scala
+++ b/domains-scalaz/.jvm/src/test/scala/com/thoughtworks/dsl/domains/scalazSpec.scala
@@ -9,8 +9,7 @@ import com.thoughtworks.dsl.keywords.{Monadic, Shift, Yield}
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
class scalazSpec extends AnyFreeSpec with Matchers {
diff --git a/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala b/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala
index abfeebaf7..d0667836f 100644
--- a/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala
+++ b/domains-scalaz/src/main/scala/com/thoughtworks/dsl/domains/scalaz.scala
@@ -14,113 +14,116 @@ 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._
- * import com.thoughtworks.dsl.Dsl.reset
- *
- * val trampoline3 = Trampoline.done(3)
- *
- * def dslSquare = Trampoline.delay {
- * s"This string is produced by a trampoline: ${!trampoline3 * !trampoline3}"
- * }: @reset
- *
- * 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")
- * }}}
- *
- *
- *
- * 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._
- * import scala.util.{Try, Success}
- * type TryTTransfomredTrampoline[A] = TryT[Trampoline, A]
- *
- * val trampolineSuccess0: TryTTransfomredTrampoline[Int] = TryT(Trampoline.done(Try(0)))
- *
- * def dslTryCatch: TryTTransfomredTrampoline[String] = TryT(Trampoline.delay(Try {
- * try {
- * s"Division result: ${!trampoline3 / !trampolineSuccess0}"
- * } catch {
- * case e: ArithmeticException =>
- * s"Cannot divide ${!trampoline3} by ${!trampolineSuccess0}"
- * }
- * })): @reset
- *
- * 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]]:
- *
- * {{{
- * 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._
+ * import com.thoughtworks.dsl.Dsl.reset
+ *
+ * val trampoline3 = Trampoline.done(3)
+ *
+ * def dslSquare = Trampoline.delay {
+ * s"This string is produced by a trampoline: ${!trampoline3 * !trampoline3}"
+ * }: @reset
+ *
+ * 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")
+ * }}}
+ *
+ *
+ *
+ * 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._
+ * import scala.util.{Try, Success}
+ * type TryTTransfomredTrampoline[A] = TryT[Trampoline, A]
+ *
+ * val trampolineSuccess0: TryTTransfomredTrampoline[Int] = TryT(Trampoline.done(Try(0)))
+ *
+ * def dslTryCatch: TryTTransfomredTrampoline[String] = TryT(Trampoline.delay(Try {
+ * try {
+ * s"Division result: ${!trampoline3 / !trampolineSuccess0}"
+ * } catch {
+ * case e: ArithmeticException =>
+ * s"Cannot divide ${!trampoline3} by ${!trampolineSuccess0}"
+ * }
+ * })): @reset
+ *
+ * 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]]:
+ *
+ * {{{
+ * 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 {
diff --git a/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/RaiiSpec.scala b/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/RaiiSpec.scala
index 8ca6dd585..ca610c3ab 100644
--- a/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/RaiiSpec.scala
+++ b/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/RaiiSpec.scala
@@ -9,8 +9,7 @@ import scala.util.control.NonFatal
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
final class RaiiSpec extends AnyFreeSpec with Matchers {
diff --git a/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/UsingSpec.scala b/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/UsingSpec.scala
index 812a63931..5b5695a1f 100644
--- a/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/UsingSpec.scala
+++ b/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/UsingSpec.scala
@@ -7,8 +7,7 @@ import org.scalatest.Assertion
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
class UsingSpec extends AnyFreeSpec with Matchers {
diff --git a/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/taskSpec.scala b/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/taskSpec.scala
index 989256261..28da861d0 100644
--- a/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/taskSpec.scala
+++ b/domains-task/.jvm/src/test/scala/com/thoughtworks/dsl/domains/taskSpec.scala
@@ -14,8 +14,7 @@ import scala.util.{Failure, Success}
import org.scalatest.freespec.AsyncFreeSpec
import org.scalatest.matchers.should.Matchers
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
final class taskSpec extends AsyncFreeSpec with Matchers {
diff --git a/domains-task/src/main/scala/com/thoughtworks/dsl/domains/task.scala b/domains-task/src/main/scala/com/thoughtworks/dsl/domains/task.scala
index 139304611..a691645af 100644
--- a/domains-task/src/main/scala/com/thoughtworks/dsl/domains/task.scala
+++ b/domains-task/src/main/scala/com/thoughtworks/dsl/domains/task.scala
@@ -14,8 +14,7 @@ import scala.util.control.{NonFatal, TailCalls}
import scala.util.{Failure, Success, Try}
import scala.util.control.TailCalls.TailRec
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
object task {
@@ -24,50 +23,49 @@ object task {
/** The asynchronous task that supports exception handling, resource management, and is stack-safe.
*
* @template
- * @example
- * A [[Task]] can be created from `for`-comprehension, where [[keywords.Each]] and [[keywords.Fork]] can be used
- * together to asynchronously iterate collections.
+ * @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.
+ * For example, the above `concatenateRemoteData` downloads and concatenates data from multiple URLs.
*
- * {{{
- * import com.thoughtworks.dsl.comprehension._
- * import com.thoughtworks.dsl.keywords._
- * import com.thoughtworks.dsl.keywords.Shift._
- * import com.thoughtworks.dsl.domains.task.Task
- * import java.net.URL
- * def concatenateRemoteData(urls: List[URL], downloader: URL => Task[Vector[Byte]]): Task[Vector[Byte]] = {
- * for {
- * url <- Fork(urls)
- * data <- downloader(url)
- * byte <- Each(data)
- * } yield byte
- * }.as[Task[Vector[Byte]]]
- * }}}
+ * {{{
+ * import com.thoughtworks.dsl.comprehension._
+ * import com.thoughtworks.dsl.keywords._
+ * import com.thoughtworks.dsl.keywords.Shift._
+ * import com.thoughtworks.dsl.domains.task.Task
+ * import java.net.URL
+ * def concatenateRemoteData(urls: List[URL], downloader: URL => Task[Vector[Byte]]): Task[Vector[Byte]] = {
+ * for {
+ * url <- Fork(urls)
+ * data <- downloader(url)
+ * byte <- Each(data)
+ * } yield byte
+ * }.as[Task[Vector[Byte]]]
+ * }}}
*
- * A [[Task]] can be also created from [[Task.apply]]
+ * A [[Task]] can be also created from [[Task.apply]]
*
- * {{{
- * def mockDownloader(url: URL) = Task {
- * "mock data\n".getBytes.toVector
- * }
- * }}}
+ * {{{
+ * 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.
+ * 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]].
+ * 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"))
+ * {{{
+ * val mockUrls = List(new URL("http://example.com/file1"), new URL("http://example.com/file2"))
*
- * import org.scalatest.Assertion
- * def assertion: Task[Assertion] = Task {
- * !concatenateRemoteData(mockUrls, mockDownloader) should be("mock data\nmock data\n".getBytes.toVector)
- * }
+ * import org.scalatest.Assertion
+ * def assertion: Task[Assertion] = Task {
+ * !concatenateRemoteData(mockUrls, mockDownloader) should be("mock data\nmock data\n".getBytes.toVector)
+ * }
*
- * Task.toFuture(assertion)
- * }}}
+ * Task.toFuture(assertion)
+ * }}}
*/
type Task[+A] = TaskDomain !! A
@@ -84,22 +82,21 @@ object task {
/** Returns a task that does nothing but let the succeeding tasks run on `executionContext`
*
- * @example
- * All the code after a `!switchExecutionContext` should be executed on `executionContext`
- * {{{
- * import com.thoughtworks.dsl.domains.task.Task
- * import org.scalatest.Assertion
- * import scala.concurrent.ExecutionContext
- * import com.thoughtworks.dsl.keywords.Shift.implicitShift
- * def myTask: Task[Assertion] = _ {
- * val originalThread = Thread.currentThread
- * !Task.switchExecutionContext(ExecutionContext.global)
- * Thread.currentThread should not be originalThread
- * }
+ * @example All the code after a `!switchExecutionContext` should be executed on `executionContext`
+ * {{{
+ * import com.thoughtworks.dsl.domains.task.Task
+ * import org.scalatest.Assertion
+ * import scala.concurrent.ExecutionContext
+ * import com.thoughtworks.dsl.keywords.Shift.implicitShift
+ * def myTask: Task[Assertion] = _ {
+ * val originalThread = Thread.currentThread
+ * !Task.switchExecutionContext(ExecutionContext.global)
+ * Thread.currentThread should not be originalThread
+ * }
*
- * Task.toFuture(myTask)
+ * Task.toFuture(myTask)
*
- * }}}
+ * }}}
*/
@inline
def switchExecutionContext(executionContext: ExecutionContext): Task[Unit] = { continue => failureHandler =>
@@ -177,8 +174,7 @@ object task {
/** Converts a [[Task]] to a [[scala.concurrent.Future]].
*
- * @see
- * [[keywords.Await]] for converting a [[scala.concurrent.Future]] to a [[Task]].
+ * @see [[keywords.Await]] for converting a [[scala.concurrent.Future]] to a [[Task]].
*/
def toFuture[A](task: Task[A]): Future[A] = {
val promise = Promise[A]()
diff --git a/keywords-AsynchronousIo/src/main/scala/com/thoughtworks/dsl/keywords/AsynchronousIo.scala b/keywords-AsynchronousIo/src/main/scala/com/thoughtworks/dsl/keywords/AsynchronousIo.scala
index 7d70936a8..5e6a10be2 100644
--- a/keywords-AsynchronousIo/src/main/scala/com/thoughtworks/dsl/keywords/AsynchronousIo.scala
+++ b/keywords-AsynchronousIo/src/main/scala/com/thoughtworks/dsl/keywords/AsynchronousIo.scala
@@ -10,68 +10,67 @@ import scala.util.control.NonFatal
/** The base keyword to perform asynchronous IO in [[domains.task.Task]]s.
*
- * @example
- * The following `readAll` is a [[com.thoughtworks.dsl.domains.task.Task Task]] to read file content with the help of
- * [[AsynchronousIo.ReadFile]]
+ * @example The following `readAll` is a [[com.thoughtworks.dsl.domains.task.Task Task]] to read file content
+ * with the help of [[AsynchronousIo.ReadFile]]
*
- * {{{
- * import java.nio._, file._, channels._
- * import com.thoughtworks.dsl.domains.task.Task
- * import com.thoughtworks.dsl.keywords._
- * import com.thoughtworks.dsl.keywords.Shift._
- * import com.thoughtworks.dsl.keywords.AsynchronousIo.ReadFile
- * import scala.collection.mutable.ArrayBuffer
- * import scala.io.Codec
- * def readAll(channel: AsynchronousFileChannel, temporaryBufferSize: Int = 4096): Task[ArrayBuffer[CharBuffer]] = Task {
- * val charBuffers = ArrayBuffer.empty[CharBuffer]
- * val decoder = Codec.UTF8.decoder
- * val byteBuffer = ByteBuffer.allocate(temporaryBufferSize)
- * var position: Long = 0L
- * while (!ReadFile(channel, byteBuffer, position) != -1) {
- * position += byteBuffer.position()
- * byteBuffer.flip()
- * charBuffers += decoder.decode(byteBuffer)
- * byteBuffer.clear()
- * }
- * charBuffers
- * }
- * }}}
+ * {{{
+ * import java.nio._, file._, channels._
+ * import com.thoughtworks.dsl.domains.task.Task
+ * import com.thoughtworks.dsl.keywords._
+ * import com.thoughtworks.dsl.keywords.Shift._
+ * import com.thoughtworks.dsl.keywords.AsynchronousIo.ReadFile
+ * import scala.collection.mutable.ArrayBuffer
+ * import scala.io.Codec
+ * def readAll(channel: AsynchronousFileChannel, temporaryBufferSize: Int = 4096): Task[ArrayBuffer[CharBuffer]] = Task {
+ * val charBuffers = ArrayBuffer.empty[CharBuffer]
+ * val decoder = Codec.UTF8.decoder
+ * val byteBuffer = ByteBuffer.allocate(temporaryBufferSize)
+ * var position: Long = 0L
+ * while (!ReadFile(channel, byteBuffer, position) != -1) {
+ * position += byteBuffer.position()
+ * byteBuffer.flip()
+ * charBuffers += decoder.decode(byteBuffer)
+ * byteBuffer.clear()
+ * }
+ * charBuffers
+ * }
+ * }}}
*
- * `Task`s created from !-notation can be used in `for`-comprehension, and other keywords can be used together in the
- * same `for` block.
+ * `Task`s created from !-notation can be used in `for`-comprehension,
+ * and other keywords can be used together in the same `for` block.
*
- * For example, the following `cat` function contains a single `for` block to concatenate file contents. It
- * asynchronously iterates elements `Seq`, `ArrayBuffer` and `String` with the help of [[keywords.Each]], managed
- * native resources with the help of [[keywords.Using]], performs previously created `readAll` task with the help of
- * [[keywords.Shift]], and finally converts the return type [[comprehension.ComprehensionOps.as as]] a
- * `Task[Vector[Char]]`.
+ * For example, the following `cat` function contains a single `for` block to concatenate file contents.
+ * It asynchronously iterates elements `Seq`, `ArrayBuffer` and `String` with the help of [[keywords.Each]],
+ * managed native resources with the help of [[keywords.Using]],
+ * performs previously created `readAll` task with the help of [[keywords.Shift]],
+ * and finally converts the return type [[comprehension.ComprehensionOps.as as]] a `Task[Vector[Char]]`.
*
- * {{{
- * import com.thoughtworks.dsl.comprehension._
- * import com.thoughtworks.dsl.keywords._
- * import com.thoughtworks.dsl.keywords.Shift._
- * import com.thoughtworks.dsl.domains.task.Task
- * import java.net.URL
- * def cat(paths: Path*) = {
- * for {
- * path <- Each(paths)
- * channel <- Using(AsynchronousFileChannel.open(path))
- * charBuffers <- readAll(channel)
- * charBuffer <- Each(charBuffers)
- * char <- Each(charBuffer.toString)
- * } yield char
- * }.as[Task[Vector[Char]]]
- * }}}
+ * {{{
+ * import com.thoughtworks.dsl.comprehension._
+ * import com.thoughtworks.dsl.keywords._
+ * import com.thoughtworks.dsl.keywords.Shift._
+ * import com.thoughtworks.dsl.domains.task.Task
+ * import java.net.URL
+ * def cat(paths: Path*) = {
+ * for {
+ * path <- Each(paths)
+ * channel <- Using(AsynchronousFileChannel.open(path))
+ * charBuffers <- readAll(channel)
+ * charBuffer <- Each(charBuffers)
+ * char <- Each(charBuffer.toString)
+ * } yield char
+ * }.as[Task[Vector[Char]]]
+ * }}}
*
- * Then the `cat` function is used to concatenate files from this project, as shown below:
+ * Then the `cat` function is used to concatenate files from this project, as shown below:
*
- * {{{
- * Task.toFuture(Task {
- * (!cat(Paths.get(".sbtopts"), Paths.get(".scalafmt.conf"))).mkString should be(
- * "-J-XX:MaxMetaspaceSize=512M\n-J-Xmx5G\n-J-Xss6M\nversion = \"1.5.1\"\nmaxColumn = 120"
- * )
- * })
- * }}}
+ * {{{
+ * Task.toFuture(Task {
+ * (!cat(Paths.get(".sbtopts"), Paths.get(".scalafmt.conf"))).mkString should be(
+ * "-J-XX:MaxMetaspaceSize=512M\n-J-Xmx5G\n-J-Xss6M\nversion = \"1.5.1\"\nmaxColumn = 120"
+ * )
+ * })
+ * }}}
*/
trait AsynchronousIo[Value] extends Any with Keyword[AsynchronousIo[Value], Value] {
diff --git a/keywords-Await/.jvm/src/test/scala/com/thoughtworks/dsl/keywords/AwaitSpec.scala b/keywords-Await/.jvm/src/test/scala/com/thoughtworks/dsl/keywords/AwaitSpec.scala
index 977f49cc5..5a5bc888c 100644
--- a/keywords-Await/.jvm/src/test/scala/com/thoughtworks/dsl/keywords/AwaitSpec.scala
+++ b/keywords-Await/.jvm/src/test/scala/com/thoughtworks/dsl/keywords/AwaitSpec.scala
@@ -16,8 +16,7 @@ import scala.concurrent.duration._
import org.scalatest.freespec.AsyncFreeSpec
import org.scalatest.matchers.should.Matchers
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
final class AwaitSpec extends AsyncFreeSpec with Matchers with BeforeAndAfterAll with Directives {
implicit val system = ActorSystem()
diff --git a/keywords-Await/src/main/scala/com/thoughtworks/dsl/keywords/Await.scala b/keywords-Await/src/main/scala/com/thoughtworks/dsl/keywords/Await.scala
index cfcc80d2a..cda5d0064 100644
--- a/keywords-Await/src/main/scala/com/thoughtworks/dsl/keywords/Await.scala
+++ b/keywords-Await/src/main/scala/com/thoughtworks/dsl/keywords/Await.scala
@@ -10,98 +10,96 @@ import scala.language.implicitConversions
/** [[Await]] is a [[Dsl.Keyword Keyword]] to extract value from a [[scala.concurrent.Future]].
*
- * This keyword is available in functions whose return types are [[scala.concurrent.Future Future]],
- * [[domains.task.Task]], or any exception aware continuations as `(_ !! Throwable !! _)`.
+ * This keyword is available in functions whose return types are
+ * [[scala.concurrent.Future Future]], [[domains.task.Task]],
+ * or any exception aware continuations as `(_ !! Throwable !! _)`.
*
- * @example
- * Given a [[scala.concurrent.Future Future]]:
- * {{{
- * import scala.concurrent.Future
- * val myFuture40 = Future {
- * 40
- * }
- * }}}
+ * @example Given a [[scala.concurrent.Future Future]]:
+ * {{{
+ * import scala.concurrent.Future
+ * val myFuture40 = Future {
+ * 40
+ * }
+ * }}}
*
- * You can [[Await]] the [[scala.concurrent.Future Future]] in another [[scala.concurrent.Future Future]]
+ * You can [[Await]] the [[scala.concurrent.Future Future]] in another [[scala.concurrent.Future Future]]
*
- * {{{
- * def myFuture42 = Future {
- * !Await(myFuture40) + 2
- * }
- * }}}
+ * {{{
+ * def myFuture42 = Future {
+ * !Await(myFuture40) + 2
+ * }
+ * }}}
*
- * A [[scala.concurrent.Future Future]] can be converted to a [[domains.task.Task]] with the help of [[Await]].
+ * A [[scala.concurrent.Future Future]] can be converted to a [[domains.task.Task]]
+ * with the help of [[Await]].
*
- * {{{
- * import com.thoughtworks.dsl.domains.task.Task
- * import com.thoughtworks.dsl.keywords.Await
- * val myTask = Task {
- * !Await(myFuture42)
- * }
- * }}}
+ * {{{
+ * import com.thoughtworks.dsl.domains.task.Task
+ * import com.thoughtworks.dsl.keywords.Await
+ * val myTask = Task {
+ * !Await(myFuture42)
+ * }
+ * }}}
*
- * Then a [[domains.task.Task]] can be converted back to a [[scala.concurrent.Future]] via
- * [[domains.task.Task.toFuture]].
+ * Then a [[domains.task.Task]] can be converted back to a [[scala.concurrent.Future]]
+ * via [[domains.task.Task.toFuture]].
*
- * {{{
- * val myAssertionTask = Task {
- * !Shift(myTask) should be(42)
- * }
- * Task.toFuture(myAssertionTask)
- * }}}
- * @example
- * `!Await` can be used together with `try` / `catch` / `finally`.
- * {{{
- * import scala.concurrent.Future
- * val buffer = new StringBuffer
- * def recoverFuture = Future {
- * buffer.append("Oh")
- * }
- * def exceptionalFuture = Future[StringBuffer] {
- * throw new IllegalStateException("No")
- * }
- * def myFuture = Future {
- * try {
- * !Await(exceptionalFuture)
- * } catch {
- * case e: IllegalStateException =>
- * !Await(recoverFuture)
- * buffer.append(' ')
- * buffer.append(e.getMessage)
- * } finally {
- * buffer.append("!")
- * }
- * }
- * myFuture.map(_.toString should be("Oh No!"))
- * }}}
- * @example
- * Other keywords, including [[Return]] or [[Get]], can be used together with [[Await]]
- * {{{
- * import scala.concurrent.Future
- * import com.thoughtworks.dsl.keywords.{Get, Return}
- * val buffer = new StringBuffer
- * def recoverFuture = Future {
- * buffer.append("Oh")
- * }
- * def exceptionalFuture = Future[StringBuffer] {
- * throw new IllegalStateException("No")
- * }
- * def myFuture: Char => Future[StringBuffer] = !Return {
- * try {
- * !Await(exceptionalFuture)
- * } catch {
- * case e: IllegalStateException =>
- * !Await(recoverFuture)
- * buffer.append(!Get[Char])
- * buffer.append(e.getMessage)
- * } finally {
- * buffer.append("!")
- * }
- * }
- * myFuture(' ').map(_.toString should be("Oh No!"))
- * }}}
- * @author
- * 杨博 (Yang Bo)
+ * {{{
+ * val myAssertionTask = Task {
+ * !Shift(myTask) should be(42)
+ * }
+ * Task.toFuture(myAssertionTask)
+ * }}}
+ * @example `!Await` can be used together with `try` / `catch` / `finally`.
+ * {{{
+ * import scala.concurrent.Future
+ * val buffer = new StringBuffer
+ * def recoverFuture = Future {
+ * buffer.append("Oh")
+ * }
+ * def exceptionalFuture = Future[StringBuffer] {
+ * throw new IllegalStateException("No")
+ * }
+ * def myFuture = Future {
+ * try {
+ * !Await(exceptionalFuture)
+ * } catch {
+ * case e: IllegalStateException =>
+ * !Await(recoverFuture)
+ * buffer.append(' ')
+ * buffer.append(e.getMessage)
+ * } finally {
+ * buffer.append("!")
+ * }
+ * }
+ * myFuture.map(_.toString should be("Oh No!"))
+ * }}}
+ * @example Other keywords, including [[Return]] or [[Get]], can be used together with [[Await]]
+ * {{{
+ * import scala.concurrent.Future
+ * import com.thoughtworks.dsl.keywords.{Get, Return}
+ * val buffer = new StringBuffer
+ * def recoverFuture = Future {
+ * buffer.append("Oh")
+ * }
+ * def exceptionalFuture = Future[StringBuffer] {
+ * throw new IllegalStateException("No")
+ * }
+ * def myFuture: Char => Future[StringBuffer] = !Return {
+ * try {
+ * !Await(exceptionalFuture)
+ * } catch {
+ * case e: IllegalStateException =>
+ * !Await(recoverFuture)
+ * buffer.append(!Get[Char])
+ * buffer.append(e.getMessage)
+ * } finally {
+ * buffer.append("!")
+ * }
+ * }
+ * myFuture(' ').map(_.toString should be("Oh No!"))
+ * }}}
+ * @author 杨博 (Yang Bo)
*/
final case class Await[Value](future: Future[Value]) extends AnyVal with Keyword[Await[Value], Value]
diff --git a/keywords-Catch/src/main/scala/com/thoughtworks/dsl/keywords/Catch.scala b/keywords-Catch/src/main/scala/com/thoughtworks/dsl/keywords/Catch.scala
index 0e06b5b1b..5769ceb8c 100644
--- a/keywords-Catch/src/main/scala/com/thoughtworks/dsl/keywords/Catch.scala
+++ b/keywords-Catch/src/main/scala/com/thoughtworks/dsl/keywords/Catch.scala
@@ -10,8 +10,7 @@ import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.Exception.Catcher
import scala.util.control.NonFatal
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
@deprecated("[[keywords.Catch]] will be removed in favor of [[Dsl.TryCatch]].", "Dsl.scala 1.4.0")
final case class Catch[Domain, Value](block: Domain !! Value, catcher: Catcher[Domain !! Value])
diff --git a/keywords-Continue/src/main/scala/com/thoughtworks/dsl/keywords/Continue.scala b/keywords-Continue/src/main/scala/com/thoughtworks/dsl/keywords/Continue.scala
index 0b2548ccd..7b17d8ca6 100644
--- a/keywords-Continue/src/main/scala/com/thoughtworks/dsl/keywords/Continue.scala
+++ b/keywords-Continue/src/main/scala/com/thoughtworks/dsl/keywords/Continue.scala
@@ -12,39 +12,34 @@ import scala.collection._
/** The base type of [[Continue$ Continue]] keyword.
*
- * @see
- * The [[Continue$ Continue]] object, which is the only instance of this [[Continue]] class.
+ * @see The [[Continue$ Continue]] object, which is the only instance of this [[Continue]] class.
*/
sealed class Continue extends Keyword[Continue, Nothing]
/** A keyword to skip the current iteration in a collection comprehension block.
*
- * @note
- * This [[Continue$ Continue]] keyword is usually used with [[Each]], to skip an element in the loop.
- * @see
- * [[Each]] for creating collection comprehensions.
- * @example
- * [[Each]] and [[Continue$ Continue]] can be used to calculate composite numbers and prime numbers.
+ * @note This [[Continue$ Continue]] keyword is usually used with [[Each]], to skip an element in the loop.
+ * @see [[Each]] for creating collection comprehensions.
+ * @example [[Each]] and [[Continue$ Continue]] can be used to calculate composite numbers and prime numbers.
*
- * {{{
- * def compositeNumbersBelow(maxNumber: Int) = collection.immutable.HashSet {
- * val factor = !Each(2 until math.ceil(math.sqrt(maxNumber)).toInt)
- * !Each(2 * factor until maxNumber by factor)
- * }
+ * {{{
+ * def compositeNumbersBelow(maxNumber: Int) = collection.immutable.HashSet {
+ * val factor = !Each(2 until math.ceil(math.sqrt(maxNumber)).toInt)
+ * !Each(2 * factor until maxNumber by factor)
+ * }
*
- * compositeNumbersBelow(13) should be(Set(4, 6, 8, 9, 10, 12))
+ * compositeNumbersBelow(13) should be(Set(4, 6, 8, 9, 10, 12))
*
- * def primeNumbersBelow(maxNumber: Int) = Seq {
- * val compositeNumbers = compositeNumbersBelow(maxNumber)
- * val i = !Each(2 until maxNumber)
- * if (compositeNumbers(i)) !Continue
- * i
- * }
+ * def primeNumbersBelow(maxNumber: Int) = Seq {
+ * val compositeNumbers = compositeNumbersBelow(maxNumber)
+ * val i = !Each(2 until maxNumber)
+ * if (compositeNumbers(i)) !Continue
+ * i
+ * }
*
- * primeNumbersBelow(13) should be(Array(2, 3, 5, 7, 11))
- * }}}
- * @author
- * 杨博 (Yang Bo)
+ * primeNumbersBelow(13) should be(Array(2, 3, 5, 7, 11))
+ * }}}
+ * @author 杨博 (Yang Bo)
*/
case object Continue extends Continue with Keyword[Continue, Nothing] {
diff --git a/keywords-Each/src/main/scala/com/thoughtworks/dsl/keywords/Each.scala b/keywords-Each/src/main/scala/com/thoughtworks/dsl/keywords/Each.scala
index edc1a420a..fdd739b81 100644
--- a/keywords-Each/src/main/scala/com/thoughtworks/dsl/keywords/Each.scala
+++ b/keywords-Each/src/main/scala/com/thoughtworks/dsl/keywords/Each.scala
@@ -11,18 +11,15 @@ import Shift.implicitShift
import scala.collection.mutable.Builder
/** Iterates though each element in [[elements]].
- * @author
- * 杨博 (Yang Bo)
+ * @author 杨博 (Yang Bo)
*
- * @example
- * [[Each]] keywords can be used to calculate cartesian product.
+ * @example [[Each]] keywords can be used to calculate cartesian product.
*
- * {{{
- * def cartesianProduct = List(!Each(Array(1, 2, 3)) * !Each(Vector(1, 10, 100, 1000)))
- * cartesianProduct should be(List(1, 10, 100, 1000, 2, 20, 200, 2000, 3, 30, 300, 3000))
- * }}}
- * @see
- * [[comprehension]] if you want to use traditional `for` comprehension instead of !-notation.
+ * {{{
+ * def cartesianProduct = List(!Each(Array(1, 2, 3)) * !Each(Vector(1, 10, 100, 1000)))
+ * cartesianProduct should be(List(1, 10, 100, 1000, 2, 20, 200, 2000, 3, 30, 300, 3000))
+ * }}}
+ * @see [[comprehension]] if you want to use traditional `for` comprehension instead of !-notation.
*/
final case class Each[Element](elements: Traversable[Element]) extends Keyword[Each[Element], Element]
object Each {
diff --git a/keywords-Each/src/test/scala/com/thoughtworks/dsl/keywords/EachSpec.scala b/keywords-Each/src/test/scala/com/thoughtworks/dsl/keywords/EachSpec.scala
index 3497c4a53..f5af243bc 100644
--- a/keywords-Each/src/test/scala/com/thoughtworks/dsl/keywords/EachSpec.scala
+++ b/keywords-Each/src/test/scala/com/thoughtworks/dsl/keywords/EachSpec.scala
@@ -4,8 +4,7 @@ import com.thoughtworks.dsl.Dsl.{!!, reset}
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
class EachSpec extends AnyFreeSpec with Matchers {
diff --git a/keywords-ForEach/src/test/scala/com/thoughtworks/dsl/keywords/ForEachSpec.scala b/keywords-ForEach/src/test/scala/com/thoughtworks/dsl/keywords/ForEachSpec.scala
index 491e8051a..196cf556c 100644
--- a/keywords-ForEach/src/test/scala/com/thoughtworks/dsl/keywords/ForEachSpec.scala
+++ b/keywords-ForEach/src/test/scala/com/thoughtworks/dsl/keywords/ForEachSpec.scala
@@ -4,8 +4,7 @@ import com.thoughtworks.dsl.Dsl.{!!, reset}
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
class ForEachSpec extends AnyFreeSpec with Matchers {
diff --git a/keywords-Get/src/main/scala/com/thoughtworks/dsl/keywords/Get.scala b/keywords-Get/src/main/scala/com/thoughtworks/dsl/keywords/Get.scala
index bfba24b3a..65b87ca0f 100644
--- a/keywords-Get/src/main/scala/com/thoughtworks/dsl/keywords/Get.scala
+++ b/keywords-Get/src/main/scala/com/thoughtworks/dsl/keywords/Get.scala
@@ -2,10 +2,8 @@ package com.thoughtworks.dsl.keywords
import com.thoughtworks.dsl.Dsl
import com.thoughtworks.dsl.Dsl.Keyword
-/** @see
- * [[Put]]
- * @author
- * 杨博 (Yang Bo)
+/** @see [[Put]]
+ * @author 杨博 (Yang Bo)
*/
final case class Get[S]() extends Keyword[Get[S], S]
diff --git a/keywords-Monadic/src/main/scala/com/thoughtworks/dsl/keywords/Monadic.scala b/keywords-Monadic/src/main/scala/com/thoughtworks/dsl/keywords/Monadic.scala
index 4d9506d90..41625bc55 100644
--- a/keywords-Monadic/src/main/scala/com/thoughtworks/dsl/keywords/Monadic.scala
+++ b/keywords-Monadic/src/main/scala/com/thoughtworks/dsl/keywords/Monadic.scala
@@ -6,12 +6,9 @@ import scala.language.implicitConversions
/** A keyword for extracting monadic value from the monadic expression [[fa]].
*
- * @see
- * [[com.thoughtworks.dsl.domains.cats]] for using this [[Monadic]] keyword with [[cats.Monad]].
- * @see
- * [[com.thoughtworks.dsl.domains.scalaz]] for using this [[Monadic]] keyword with [[scalaz.Monad]].
- * @todo
- * [[Monadic]] should be a [[scala.AnyVal]] after [[https://github.com/scala/bug/issues/10595]] is resolved.
+ * @see [[com.thoughtworks.dsl.domains.cats]] for using this [[Monadic]] keyword with [[cats.Monad]].
+ * @see [[com.thoughtworks.dsl.domains.scalaz]] for using this [[Monadic]] keyword with [[scalaz.Monad]].
+ * @todo [[Monadic]] should be a [[scala.AnyVal]] after [[https://github.com/scala/bug/issues/10595]] is resolved.
*/
final case class Monadic[F[_], A](fa: F[A]) extends Keyword[Monadic[F, A], A]
diff --git a/keywords-NullSafe/src/main/scala/com/thoughtworks/dsl/keywords/NullSafe.scala b/keywords-NullSafe/src/main/scala/com/thoughtworks/dsl/keywords/NullSafe.scala
index 1fad535cc..b0d2bfa6d 100644
--- a/keywords-NullSafe/src/main/scala/com/thoughtworks/dsl/keywords/NullSafe.scala
+++ b/keywords-NullSafe/src/main/scala/com/thoughtworks/dsl/keywords/NullSafe.scala
@@ -9,100 +9,96 @@ import scala.annotation.compileTimeOnly
/** [[NullSafe]] is a keyword to perform `null` check.
*
- * @example
- * You can use [[NullSafe$.? ?]] annotation to represent a nullable value.
+ * @example You can use [[NullSafe$.? ?]] annotation to represent a nullable value.
*
- * {{{
- * import com.thoughtworks.dsl.keywords.NullSafe._
+ * {{{
+ * import com.thoughtworks.dsl.keywords.NullSafe._
*
- * case class Tree(left: Tree @ $qmark = null, right: Tree @ $qmark = null, value: String @ $qmark = null)
+ * case class Tree(left: Tree @ $qmark = null, right: Tree @ $qmark = null, value: String @ $qmark = null)
*
- * val root: Tree @ $qmark = Tree(
- * left = Tree(
- * left = Tree(value = "left-left"),
- * right = Tree(value = "left-right")
- * ),
- * right = Tree(value = "right")
- * )
- * }}}
+ * val root: Tree @ $qmark = Tree(
+ * left = Tree(
+ * left = Tree(value = "left-left"),
+ * right = Tree(value = "left-right")
+ * ),
+ * right = Tree(value = "right")
+ * )
+ * }}}
*
- * A normal `.` is not null safe, when selecting `left`, `right` or `value` on a `null` value.
+ * A normal `.` is not null safe, when selecting `left`, `right` or `value` on a `null` value.
*
- * {{{
- * a[NullPointerException] should be thrownBy {
- * root.right.left.right.value
- * }
- * }}}
+ * {{{
+ * a[NullPointerException] should be thrownBy {
+ * root.right.left.right.value
+ * }
+ * }}}
*
- * The above code throws an exception because `root.right.left` is `null`.
+ * The above code throws an exception because `root.right.left` is `null`.
*
- * The exception can be avoided by using [[?]] on a nullable value:
+ * The exception can be avoided by using [[?]] on a nullable value:
*
- * {{{
- * root.?.right.?.left.?.right.?.value should be(null)
- * }}}
+ * {{{
+ * root.?.right.?.left.?.right.?.value should be(null)
+ * }}}
*
- * The entire expression will be `null` if one of [[?]] is performed on a `null` value.
+ * The entire expression will be `null` if one of [[?]] is performed on a `null` value.
*
- *
+ *
*
- * The boundary of a null safe operator [[?]] is the nearest enclosing expression whose type is annotated as `@ ?`.
+ * The boundary of a null safe operator [[?]] is the nearest enclosing expression
+ * whose type is annotated as `@ ?`.
*
- * {{{
- * ("Hello " + ("world " + root.?.right.?.left.?.value)) should be("Hello world null")
- * ("Hello " + (("world " + root.?.right.?.left.?.value.?): @ $qmark)) should be("Hello null")
- * (("Hello " + ("world " + root.?.right.?.left.?.value.?)): @ $qmark) should be(null)
- * }}}
+ * {{{
+ * ("Hello " + ("world " + root.?.right.?.left.?.value)) should be("Hello world null")
+ * ("Hello " + (("world " + root.?.right.?.left.?.value.?): @ $qmark)) should be("Hello null")
+ * (("Hello " + ("world " + root.?.right.?.left.?.value.?)): @ $qmark) should be(null)
+ * }}}
*
- * @example
- * The [[?]] operator usually works with Java libraries that may produce `null`.
+ * @example The [[?]] operator usually works with Java libraries that may produce `null`.
*
- * {{{
- * import com.thoughtworks.dsl.keywords.NullSafe._
+ * {{{
+ * import com.thoughtworks.dsl.keywords.NullSafe._
*
- * val myMap = new java.util.HashMap[String, String]();
- * ((myMap.get("key1").? + myMap.get("key2").?): @ $qmark) should be(null)
- * }}}
+ * val myMap = new java.util.HashMap[String, String]();
+ * ((myMap.get("key1").? + myMap.get("key2").?): @ $qmark) should be(null)
+ * }}}
*
- * @note
- * The [[?]] operator is only available on nullable values.
+ * @note The [[?]] operator is only available on nullable values.
*
- * A type is considered as nullable if it is a reference type, no matter it is annotated as `@ ?` or not.
+ * A type is considered as nullable if it is a reference type,
+ * no matter it is annotated as `@ ?` or not.
*
- * {{{
+ * {{{
* import com.thoughtworks.dsl.keywords.NullSafe._
*
* val explicitNullable: String @ $qmark = null
* ((explicitNullable.? + " Doe") : @ $qmark) should be(null)
- * }}}
+ * }}}
*
- * {{{
+ * {{{
* val implicitNullable: String = null
* ((implicitNullable.? + " Doe") : @ $qmark) should be(null)
- * }}}
+ * }}}
*
- * A type is considered as not nullable if it is a value type.
+ * A type is considered as not nullable if it is a value type.
*
- * {{{
+ * {{{
* val implicitNotNullable: Int = 0
* "(implicitNotNullable.? + 42) : @ $qmark" shouldNot compile
- * }}}
+ * }}}
*
- * Alternatively, a type can be considered as not nullable by explicitly converting it to
- * [[com.thoughtworks.dsl.keywords.NullSafe.NotNull[A]* NotNull]].
+ * Alternatively, a type can be considered as not nullable
+ * by explicitly converting it to [[com.thoughtworks.dsl.keywords.NullSafe.NotNull[A]* NotNull]].
*
- * {{{
+ * {{{
* val explicitNotNullable: NotNull[String] = NotNull("John")
* """(explicitNotNullable.? + " Doe") : @ $qmark""" shouldNot compile
- * }}}
+ * }}}
*
- * @see
- * [[NoneSafe]] for similar checks on [[scala.Option]]s.
- * @author
- * 杨博 (Yang Bo)
+ * @see [[NoneSafe]] for similar checks on [[scala.Option]]s.
+ * @author 杨博 (Yang Bo)
*
- * @define qmark
- * ?
+ * @define qmark ?
*/
final case class NullSafe[A <: AnyRef](nullable: A @ ?) extends AnyVal {
@@ -140,32 +136,29 @@ object NullSafe {
private[NullSafe] def toNotNull[A](a: A) = a
}
- /** @usecase
- * type NotNull[+A] <: A
+ /** @usecase type NotNull[+A] <: A
*/
type NotNull[+A] = OpaqueTypes.NotNull[A]
/** Returns `a` if `a` is not `null`.
*
- * @return
- * `a` if `a` is not `null`.
+ * @return `a` if `a` is not `null`.
*
- * {{{
+ * {{{
* import com.thoughtworks.dsl.keywords.NullSafe._
*
* val o = new AnyRef
* NotNull(o) should be(o)
- * }}}
+ * }}}
*
- * @throws java.lang.NullPointerException
- * if `a` is `null`.
+ * @throws java.lang.NullPointerException if `a` is `null`.
*
- * {{{
+ * {{{
* import com.thoughtworks.dsl.keywords.NullSafe._
* a[NullPointerException] should be thrownBy {
* NotNull(null)
* }
- * }}}
+ * }}}
*/
def NotNull[A](a: A): NotNull[A] = {
if (a == null) {
diff --git a/keywords-Put/src/main/scala/com/thoughtworks/dsl/keywords/Put.scala b/keywords-Put/src/main/scala/com/thoughtworks/dsl/keywords/Put.scala
index 67ecbe6fd..042b26635 100644
--- a/keywords-Put/src/main/scala/com/thoughtworks/dsl/keywords/Put.scala
+++ b/keywords-Put/src/main/scala/com/thoughtworks/dsl/keywords/Put.scala
@@ -4,56 +4,52 @@ import com.thoughtworks.dsl.Dsl.Keyword
/** [[Put]] is a [[Dsl.Keyword Keyword]] to replace the [[value]] of the current context.
*
- * Purely functional programming languages usually do not support native first-class mutable variables. In those
- * languages, mutable states can be implemented in state monads.
+ * Purely functional programming languages usually do not support native first-class mutable variables.
+ * In those languages, mutable states can be implemented in state monads.
*
* [[Put]] and [[Get]] are the [[Dsl]]-based replacements of state monads.
*
- * We use unary function as the domain of mutable state. The parameter of the unary function can be read from the
- * [[Get]] keyword, and changed by the [[Put]] keyword.
+ * We use unary function as the domain of mutable state.
+ * The parameter of the unary function can be read from the [[Get]] keyword, and changed by the [[Put]] keyword.
*
- * @example
- * The following example creates a function that accepts a string parameter and returns the upper-cased last
- * character of the parameter.
+ * @example The following example creates a function that accepts a string parameter
+ * and returns the upper-cased last character of the parameter.
*
- * {{{
- * def upperCasedLastCharacter: String => Char = {
- * val initialValue = !Get[String]()
- * !Put(initialValue.toUpperCase)
+ * {{{
+ * def upperCasedLastCharacter: String => Char = {
+ * val initialValue = !Get[String]()
+ * !Put(initialValue.toUpperCase)
*
- * val upperCased = !Get[String]()
- * Function.const(upperCased.last)
- * }
- * }}}
+ * val upperCased = !Get[String]()
+ * Function.const(upperCased.last)
+ * }
+ * }}}
*
- * For example, given a string of `foo`, the upper-cased last character should be `O`.
+ * For example, given a string of `foo`, the upper-cased last character should be `O`.
*
- * {{{
- * // Output: O
- * upperCasedLastCharacter("foo") should be('O')
- * }}}
+ * {{{
+ * // Output: O
+ * upperCasedLastCharacter("foo") should be('O')
+ * }}}
*
- * @example
- * [[Put]] and [[Get]] support multiple states.
+ * @example [[Put]] and [[Get]] support multiple states.
*
- * The following code creates a formatter that [[Put]] parts of content into a `Vector[Any]` of string buffers.
+ * The following code creates a formatter that [[Put]] parts of content into a `Vector[Any]` of string buffers.
*
- * {{{
- * def formatter: Double => Int => Vector[Any] => String = {
- * !Put(!Get[Vector[Any]] :+ "x=")
- * !Put(!Get[Vector[Any]] :+ !Get[Double])
- * !Put(!Get[Vector[Any]] :+ ",y=")
- * !Put(!Get[Vector[Any]] :+ !Get[Int])
+ * {{{
+ * def formatter: Double => Int => Vector[Any] => String = {
+ * !Put(!Get[Vector[Any]] :+ "x=")
+ * !Put(!Get[Vector[Any]] :+ !Get[Double])
+ * !Put(!Get[Vector[Any]] :+ ",y=")
+ * !Put(!Get[Vector[Any]] :+ !Get[Int])
*
- * !Return((!Get[Vector[Any]]).mkString)
- * }
+ * !Return((!Get[Vector[Any]]).mkString)
+ * }
*
- * formatter(0.5)(42)(Vector.empty) should be("x=0.5,y=42")
- * }}}
- * @see
- * [[Get]]
- * @author
- * 杨博 (Yang Bo)
+ * formatter(0.5)(42)(Vector.empty) should be("x=0.5,y=42")
+ * }}}
+ * @see [[Get]]
+ * @author 杨博 (Yang Bo)
*/
final case class Put[S](value: S) extends AnyVal with Keyword[Put[S], Unit]
diff --git a/keywords-Return/src/main/scala/com/thoughtworks/dsl/keywords/Return.scala b/keywords-Return/src/main/scala/com/thoughtworks/dsl/keywords/Return.scala
index c0ae09088..24c5e4294 100644
--- a/keywords-Return/src/main/scala/com/thoughtworks/dsl/keywords/Return.scala
+++ b/keywords-Return/src/main/scala/com/thoughtworks/dsl/keywords/Return.scala
@@ -7,53 +7,52 @@ import scala.language.implicitConversions
/** A [[Dsl.Keyword]] to early return a lifted value from the enclosing function.
*
- * @author
- * 杨博 (Yang Bo)
- * @example
- * Suppose you are generating a random integer less than 100, whose first digit and second digit is different. A
- * solution is generating integers in an infinite loop, and [[Return]] from the loop when the generated integer
- * conforms with requirements.
+ * @author 杨博 (Yang Bo)
+ * @example Suppose you are generating a random integer less than 100,
+ * whose first digit and second digit is different.
+ * A solution is generating integers in an infinite loop,
+ * and [[Return]] from the loop when the generated integer conforms with requirements.
*
- * {{{
- * import scala.util.Random
- * import scala.util.control.TailCalls
- * import scala.util.control.TailCalls.TailRec
- * def randomInt(): TailRec[Int] = {
- * while (true) {
- * val r = Random.nextInt(100)
- * if (r % 10 != r / 10) {
- * !Return(TailCalls.done(r))
- * }
- * }
- * throw new AssertionError("Unreachable code");
- * }
+ * {{{
+ * import scala.util.Random
+ * import scala.util.control.TailCalls
+ * import scala.util.control.TailCalls.TailRec
+ * def randomInt(): TailRec[Int] = {
+ * while (true) {
+ * val r = Random.nextInt(100)
+ * if (r % 10 != r / 10) {
+ * !Return(TailCalls.done(r))
+ * }
+ * }
+ * throw new AssertionError("Unreachable code");
+ * }
*
- * val r = randomInt().result
- * r should be < 100
- * r % 10 should not be r / 10
- * }}}
+ * val r = randomInt().result
+ * r should be < 100
+ * r % 10 should not be r / 10
+ * }}}
*
- * @example
- * Since this [[Return]] keyword can automatically lift the return type, `TailCalls.done` can be omitted.
+ * @example Since this [[Return]] keyword can automatically lift the return type,
+ * `TailCalls.done` can be omitted.
*
- * {{{
- * import scala.util.Random
- * import scala.util.control.TailCalls
- * import scala.util.control.TailCalls.TailRec
- * def randomInt(): TailRec[Int] = {
- * while (true) {
- * val r = Random.nextInt(100)
- * if (r % 10 != r / 10) {
- * !Return(r)
- * }
- * }
- * throw new AssertionError("Unreachable code");
- * }
+ * {{{
+ * import scala.util.Random
+ * import scala.util.control.TailCalls
+ * import scala.util.control.TailCalls.TailRec
+ * def randomInt(): TailRec[Int] = {
+ * while (true) {
+ * val r = Random.nextInt(100)
+ * if (r % 10 != r / 10) {
+ * !Return(r)
+ * }
+ * }
+ * throw new AssertionError("Unreachable code");
+ * }
*
- * val r = randomInt().result
- * r should be < 100
- * r % 10 should not be r / 10
- * }}}
+ * val r = randomInt().result
+ * r should be < 100
+ * r % 10 should not be r / 10
+ * }}}
*/
final case class Return[ReturnValue](returnValue: ReturnValue) extends AnyVal with Keyword[Return[ReturnValue], Nothing]
diff --git a/keywords-Return/src/test/scala/com/thoughtworks/dsl/keywords/ReturnSpec.scala b/keywords-Return/src/test/scala/com/thoughtworks/dsl/keywords/ReturnSpec.scala
index e93fff6a2..247894188 100644
--- a/keywords-Return/src/test/scala/com/thoughtworks/dsl/keywords/ReturnSpec.scala
+++ b/keywords-Return/src/test/scala/com/thoughtworks/dsl/keywords/ReturnSpec.scala
@@ -3,8 +3,7 @@ import com.thoughtworks.dsl.Dsl.!!
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
final class ReturnSpec extends AnyFreeSpec with Matchers {
diff --git a/keywords-Shift/src/main/scala/com/thoughtworks/dsl/keywords/Shift.scala b/keywords-Shift/src/main/scala/com/thoughtworks/dsl/keywords/Shift.scala
index 793526f79..c671ec42d 100644
--- a/keywords-Shift/src/main/scala/com/thoughtworks/dsl/keywords/Shift.scala
+++ b/keywords-Shift/src/main/scala/com/thoughtworks/dsl/keywords/Shift.scala
@@ -9,8 +9,7 @@ import scala.language.implicitConversions
import scala.util.control.{NonFatal, TailCalls}
import scala.util.control.TailCalls.TailRec
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
final case class Shift[Domain, Value](continuation: Domain !! Value)
extends AnyVal
diff --git a/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala b/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala
index a13068e89..80e34608d 100644
--- a/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala
+++ b/keywords-Using/src/main/scala/com/thoughtworks/dsl/keywords/Using.scala
@@ -10,13 +10,11 @@ import scala.concurrent.{ExecutionContext, Future}
import scala.language.implicitConversions
import scala.util.control.NonFatal
-/** This [[Using]] keyword automatically manage resources in [[scala.concurrent.Future]], [[domains.task.Task]], and
- * other asynchrounous domains derived from `Future` or `Task`.
+/** This [[Using]] keyword automatically manage resources in [[scala.concurrent.Future]], [[domains.task.Task]],
+ * and other asynchrounous domains derived from `Future` or `Task`.
*
- * @author
- * 杨博 (Yang Bo)
- * @see
- * [[dsl]] for usage of this [[Using]] keyword in continuations
+ * @author 杨博 (Yang Bo)
+ * @see [[dsl]] for usage of this [[Using]] keyword in continuations
*/
final case class Using[R <: AutoCloseable](open: () => R) extends AnyVal with Keyword[Using[R], R]
@@ -26,37 +24,36 @@ object Using {
trait ScopeExitHandler extends AutoCloseable
- /** Returns a [[Using]] keyword to execute a [[ScopeExitHandler]] when exiting the nearest enclosing scope that is
- * annotated as [[Dsl.reset @reset]], (or the nearest enclosing function if [[compilerplugins.ResetEverywhere]] is
- * enabled).
+ /** Returns a [[Using]] keyword to execute a [[ScopeExitHandler]] when exiting the nearest enclosing scope
+ * that is annotated as [[Dsl.reset @reset]],
+ * (or the nearest enclosing function if [[compilerplugins.ResetEverywhere]] is enabled).
*
- * @note
- * This method is similar to [[apply]], except the parameter type is changed from a generic `R` to the SAM type
- * [[ScopeExitHandler]], which allows for function literal expressions in Scala 2.12+ or Scala 2.11 with
- * `-Xexperimental` compiler option.
+ * @note This method is similar to [[apply]],
+ * except the parameter type is changed from a generic `R` to the SAM type [[ScopeExitHandler]],
+ * which allows for function literal expressions
+ * in Scala 2.12+ or Scala 2.11 with `-Xexperimental` compiler option.
*
- * @example
- * The following function will perform `n *= 2` after `n += 20`:
+ * @example The following function will perform `n *= 2` after `n += 20`:
*
- * {{{
- * import scala.concurrent.Future
- * import com.thoughtworks.dsl.keywords.Using.scopeExit
- * var n = 1
- * def multiplicationAfterAddition = Future {
- * !scopeExit { () =>
- * n *= 2
- * }
- * n += 20
- * }
- * }}}
+ * {{{
+ * import scala.concurrent.Future
+ * import com.thoughtworks.dsl.keywords.Using.scopeExit
+ * var n = 1
+ * def multiplicationAfterAddition = Future {
+ * !scopeExit { () =>
+ * n *= 2
+ * }
+ * n += 20
+ * }
+ * }}}
*
- * Therefore, the final value of `n` should be `(1 + 20) * 2 = 42`.
+ * Therefore, the final value of `n` should be `(1 + 20) * 2 = 42`.
*
- * {{{
- * multiplicationAfterAddition.map { _ =>
- * n should be(42)
- * }
- * }}}
+ * {{{
+ * multiplicationAfterAddition.map { _ =>
+ * n should be(42)
+ * }
+ * }}}
*/
def scopeExit(r: => ScopeExitHandler) = new Using(r _)
diff --git a/keywords-Yield/src/main/scala/com/thoughtworks/dsl/keywords/Yield.scala b/keywords-Yield/src/main/scala/com/thoughtworks/dsl/keywords/Yield.scala
index 8d24ed490..089c2a20c 100644
--- a/keywords-Yield/src/main/scala/com/thoughtworks/dsl/keywords/Yield.scala
+++ b/keywords-Yield/src/main/scala/com/thoughtworks/dsl/keywords/Yield.scala
@@ -12,33 +12,29 @@ import scala.concurrent.Future
import scala.language.implicitConversions
import scala.language.higherKinds
-/** @author
- * 杨博 (Yang Bo)
- * @example
- * This `Yield` keyword must be put inside a function that returns `Seq[Element]` or `Seq[Element] !! ...`, or it
- * will not compile.
+/** @author 杨博 (Yang Bo)
+ * @example This `Yield` keyword must be put inside a function that returns `Seq[Element]` or `Seq[Element] !! ...`,
+ * or it will not compile.
*
- * {{{
- * "def f(): Int = !Yield(1)" shouldNot compile
- * }}}
+ * {{{
+ * "def f(): Int = !Yield(1)" shouldNot compile
+ * }}}
*
- * @example
- * [[Yield]] keywords can be used together with other keywords.
- * {{{
- * def gccFlagBuilder(sourceFile: String, includes: String*): Stream[String] = {
- * !Yield("gcc")
- * !Yield("-c")
- * !Yield(sourceFile)
- * val include = !Each(includes)
- * !Yield("-I")
- * !Yield(include)
- * !Continue
- * }
+ * @example [[Yield]] keywords can be used together with other keywords.
+ * {{{
+ * def gccFlagBuilder(sourceFile: String, includes: String*): Stream[String] = {
+ * !Yield("gcc")
+ * !Yield("-c")
+ * !Yield(sourceFile)
+ * val include = !Each(includes)
+ * !Yield("-I")
+ * !Yield(include)
+ * !Continue
+ * }
*
- * gccFlagBuilder("main.c", "lib1/include", "lib2/include") should be(Stream("gcc", "-c", "main.c", "-I", "lib1/include", "-I", "lib2/include"))
- * }}}
- * @see
- * [[comprehension]] if you want to use traditional `for` comprehension instead of !-notation.
+ * gccFlagBuilder("main.c", "lib1/include", "lib2/include") should be(Stream("gcc", "-c", "main.c", "-I", "lib1/include", "-I", "lib2/include"))
+ * }}}
+ * @see [[comprehension]] if you want to use traditional `for` comprehension instead of !-notation.
*/
final case class Yield[Element](element: Element) extends AnyVal with Keyword[Yield[Element], Unit]
diff --git a/keywords-Yield/src/test/scala-2.13/com/thoughtworks/dsl/keywords/YieldSpec213.scala b/keywords-Yield/src/test/scala-2.13/com/thoughtworks/dsl/keywords/YieldSpec213.scala
index 5741bfb6e..91a84c059 100644
--- a/keywords-Yield/src/test/scala-2.13/com/thoughtworks/dsl/keywords/YieldSpec213.scala
+++ b/keywords-Yield/src/test/scala-2.13/com/thoughtworks/dsl/keywords/YieldSpec213.scala
@@ -9,8 +9,7 @@ import scala.annotation.tailrec
import scala.collection.{LinearSeq, SeqView}
import scala.runtime.NonLocalReturnControl
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
class YieldSpec213 extends AnyFreeSpec with Matchers {
diff --git a/keywords-Yield/src/test/scala/com/thoughtworks/dsl/keywords/YieldSpec.scala b/keywords-Yield/src/test/scala/com/thoughtworks/dsl/keywords/YieldSpec.scala
index 0d7ec67a0..aca3a0532 100644
--- a/keywords-Yield/src/test/scala/com/thoughtworks/dsl/keywords/YieldSpec.scala
+++ b/keywords-Yield/src/test/scala/com/thoughtworks/dsl/keywords/YieldSpec.scala
@@ -10,8 +10,7 @@ import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.Assertions
import org.scalatest.matchers.should.Matchers
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
class YieldSpec extends AnyFreeSpec with Matchers with Assertions {
diff --git a/package/src/main/scala/com/thoughtworks/dsl/package.scala b/package/src/main/scala/com/thoughtworks/dsl/package.scala
index 365748d76..838d2f999 100644
--- a/package/src/main/scala/com/thoughtworks/dsl/package.scala
+++ b/package/src/main/scala/com/thoughtworks/dsl/package.scala
@@ -2,582 +2,600 @@ package com.thoughtworks
/** This project, '''Dsl.scala''', is a framework to create embedded '''D'''omain-'''S'''pecific '''L'''anguages.
*
- * DSLs written in '''Dsl.scala''' are collaborative with others DSLs and Scala control flows. DSL users can create
- * functions that contains interleaved DSLs implemented by different vendors, along with ordinary Scala control flows.
+ * DSLs written in '''Dsl.scala''' are collaborative with others DSLs and Scala control flows.
+ * DSL users can create functions that contains interleaved DSLs implemented by different vendors,
+ * along with ordinary Scala control flows.
*
- * We also provide some built-in DSLs for asynchronous programming, collection manipulation, and adapters to
- * [[scalaz.Monad]] or [[cats.Monad]]. Those built-in DSLs can be used as a replacement of
+ * We also provide some built-in DSLs for asynchronous programming, collection manipulation,
+ * and adapters to [[scalaz.Monad]] or [[cats.Monad]].
+ * Those built-in DSLs can be used as a replacement of
* [[https://docs.scala-lang.org/tour/for-comprehensions.html `for` comprehension]],
* [[https://github.com/scala/scala-continuations scala-continuations]],
- * [[https://github.com/scala/scala-async scala-async]], [[http://monadless.io/ Monadless]],
- * [[https://github.com/pelotom/effectful effectful]] and
- * [[https://github.com/ThoughtWorksInc/each ThoughtWorks Each]].
+ * [[https://github.com/scala/scala-async scala-async]],
+ * [[http://monadless.io/ Monadless]],
+ * [[https://github.com/pelotom/effectful effectful]]
+ * and [[https://github.com/ThoughtWorksInc/each ThoughtWorks Each]].
*
- * =Introduction=
+ * = Introduction =
*
- * ==Reinventing control flow in DSL==
+ * == Reinventing control flow in DSL ==
*
- * Embedded DSLs usually consist of a set of domain-specific keywords, which can be embedded in the their hosting
- * languages.
+ * Embedded DSLs usually consist of a set of domain-specific keywords,
+ * which can be embedded in the their hosting languages.
*
- * Ideally, a domain-specific keyword should be an optional extension, which can be present everywhere in the ordinary
- * control flow of the hosting language. However, previous embedded DSLs usually badly interoperate with hosting
- * language control flow. Instead, they reinvent control flow in their own DSL.
+ * Ideally, a domain-specific keyword should be an optional extension,
+ * which can be present everywhere in the ordinary control flow of the hosting language.
+ * However, previous embedded DSLs usually badly interoperate with hosting language control flow.
+ * Instead, they reinvent control flow in their own DSL.
*
* For example, the [[https://akka.io akka]] provides
- * [[https://doc.akka.io/docs/akka/2.5.10/fsm.html a DSL to create finite-state machines]], which consists of some
- * domain-specific keywords like [[akka.actor.FSM!.when when]], [[akka.actor.FSM!.goto goto]] and
- * [[akka.actor.FSM!.stay stay]]. Unfortunately, you cannot embedded those keywords into your ordinary `if` / `while` /
- * `try` control flows, because Akka's DSL is required to be split into small closures, preventing ordinary control
- * flows from crossing the boundary of those closures.
- *
- * TensorFlow's [[https://www.tensorflow.org/api_guides/python/control_flow_ops control flow operations]] and Caolan's
- * [[https://github.com/caolan/async async]] library are examples of reinventing control flow in languages other than
- * Scala.
- *
- * ==Monad: the generic interface of control flow==
- *
- * It's too trivial to reinvent the whole set of control flows for each DSL. A simpler approach is only implementing a
- * minimal interface required for control flows for each domain, while the syntax of other control flow operations are
- * derived from the interface, shared between different domains.
- *
- * Since
- * [[https://www.sciencedirect.com/science/article/pii/0890540191900524 computation can be represented as monads]],
- * some libraries use monad as the interface of control flow, including [[scalaz.Monad]], [[cats.Monad]] and
- * [[com.twitter.algebird.Monad]]. A DSL author only have to implement two abstract method in [[scalaz.Monad]], and all
- * the derived control flow operations like [[scalaz.syntax.MonadOps.whileM]], [[scalaz.syntax.BindOps.ifM]] are
- * available. In addition, those monadic data type can be created and composed from Scala's built-in
- * [[https://docs.scala-lang.org/tour/for-comprehensions.html `for` comprehension]].
- *
- * For example, you can use the same [[scalaz.syntax syntax]] or `for` comprehension to create
- * [[org.scalacheck.Gen random value generators]] and [[com.thoughtworks.binding.Binding data-binding expressions]], as
- * long as there are [[scalaz.Monad Monad]] instances for [[org.scalacheck.Gen]] and
- * [[com.thoughtworks.binding.Binding]] respectively.
- *
- * Although the effort of creating a DSL is minimized with the help of monads, the syntax is still unsatisfactory.
- * Methods in `MonadOps` still seem like a duplicate of ordinary control flow, and `for` comprehension supports only a
- * limited set of functionality in comparison to ordinary control flows. `if` / `while` / `try` and other block
- * expressions cannot appear in the enumerator clause of `for` comprehension.
- *
- * ==Enabling ordinary control flows in DSL via macros==
- *
- * An idea to avoid inconsistency between domain-specific control flow and ordinary control flow is converting ordinary
- * control flow to domain-specific control flow at compiler time.
- *
- * For example, [[https://github.com/scala/scala-async scala.async]] provides a macro to generate asynchronous control
- * flow. The users just wrap normal synchronous code in a [[scala.async.Async.async async]] block, and it runs
- * asynchronously.
+ * [[https://doc.akka.io/docs/akka/2.5.10/fsm.html a DSL to create finite-state machines]],
+ * which consists of some domain-specific keywords like [[akka.actor.FSM!.when when]],
+ * [[akka.actor.FSM!.goto goto]] and [[akka.actor.FSM!.stay stay]].
+ * Unfortunately, you cannot embedded those keywords into your ordinary `if` / `while` / `try` control flows,
+ * because Akka's DSL is required to be split into small closures,
+ * preventing ordinary control flows from crossing the boundary of those closures.
+ *
+ * TensorFlow's [[https://www.tensorflow.org/api_guides/python/control_flow_ops control flow operations]] and
+ * Caolan's [[https://github.com/caolan/async async]] library are examples of reinventing control flow
+ * in languages other than Scala.
+ *
+ * == Monad: the generic interface of control flow ==
+ *
+ * It's too trivial to reinvent the whole set of control flows for each DSL.
+ * A simpler approach is only implementing a minimal interface required for control flows for each domain,
+ * while the syntax of other control flow operations are derived from the interface, shared between different domains.
+ *
+ * Since [[https://www.sciencedirect.com/science/article/pii/0890540191900524 computation can be represented as monads]],
+ * some libraries use monad as the interface of control flow,
+ * including [[scalaz.Monad]], [[cats.Monad]] and [[com.twitter.algebird.Monad]].
+ * A DSL author only have to implement two abstract method in [[scalaz.Monad]],
+ * and all the derived control flow operations
+ * like [[scalaz.syntax.MonadOps.whileM]], [[scalaz.syntax.BindOps.ifM]] are available.
+ * In addition, those monadic data type can be created and composed
+ * from Scala's built-in [[https://docs.scala-lang.org/tour/for-comprehensions.html `for` comprehension]].
+ *
+ * For example, you can use the same [[scalaz.syntax syntax]] or `for` comprehension
+ * to create [[org.scalacheck.Gen random value generators]]
+ * and [[com.thoughtworks.binding.Binding data-binding expressions]],
+ * as long as there are [[scalaz.Monad Monad]] instances
+ * for [[org.scalacheck.Gen]] and [[com.thoughtworks.binding.Binding]] respectively.
+ *
+ * Although the effort of creating a DSL is minimized with the help of monads,
+ * the syntax is still unsatisfactory.
+ * Methods in `MonadOps` still seem like a duplicate of ordinary control flow,
+ * and `for` comprehension supports only a limited set of functionality in comparison to ordinary control flows.
+ * `if` / `while` / `try` and other block expressions cannot appear in the enumerator clause of `for` comprehension.
+ *
+ * == Enabling ordinary control flows in DSL via macros ==
+ *
+ * An idea to avoid inconsistency between domain-specific control flow and ordinary control flow is
+ * converting ordinary control flow to domain-specific control flow at compiler time.
+ *
+ * For example, [[https://github.com/scala/scala-async scala.async]] provides a macro
+ * to generate asynchronous control flow.
+ * The users just wrap normal synchronous code in a [[scala.async.Async.async async]] block,
+ * and it runs asynchronously.
*
* This approach can be generalized to any monadic data types.
- * [[https://github.com/ThoughtWorksInc/each ThoughtWorks Each]], [[http://monadless.io/ Monadless]] and
- * [[https://github.com/pelotom/effectful effectful]] are macros that convert ordinary control flow to monadic control
- * flow.
+ * [[https://github.com/ThoughtWorksInc/each ThoughtWorks Each]], [[http://monadless.io/ Monadless]]
+ * and [[https://github.com/pelotom/effectful effectful]] are macros
+ * that convert ordinary control flow to monadic control flow.
*
* For example, with the help of [[https://github.com/ThoughtWorksInc/each ThoughtWorks Each]],
- * [[https://github.com/ThoughtWorksInc/Binding.scala Binding.scala]] is used to create reactive HTML templating from
- * ordinary Scala control flow.
- *
- * ==Delimited continuations==
- *
- * Another generic interface of control flow is continuation, which is known as
- * [[https://www.schoolofhaskell.com/user/dpiponi/the-mother-of-all-monads the mother of all monads]], where control
- * flows in specific domain can be supported by specific final result types of continuations.
- *
- * [[https://github.com/scala/scala-continuations scala-continuations]] and
- * [[https://github.com/qifun/stateless-future stateless-future]] are two delimited continuation implementations. Both
- * projects can convert ordinary control flow to continuation-passing style closure chains at compiler time.
- *
- * For example, [[https://github.com/qifun/stateless-future-akka stateless-future-akka]], based on `stateless-future`,
- * provides a special final result type for akka actors. Unlike [[akka.actor.FSM]]'s inconsistent control flows, users
- * can create complex finite-state machines from simple ordinary control flows along with `stateless-future-akka`'s
- * domain-specific keyword `nextMessage`.
- *
- * ==Collaborative DSLs==
- *
- * All the above approaches lack of the ability to collaborate with other DSLs. Each of the above DSLs can be only
- * exclusively enabled in a code block. For example,
- * [[https://github.com/scala/scala-continuations scala-continuations]] enables calls to `@cps` method in
- * [[scala.util.continuations.reset]] blocks, and [[https://github.com/ThoughtWorksInc/each ThoughtWorks Each]] enables
- * the magic `each` method for [[scalaz.Monad]] in [[com.thoughtworks.each.Monadic.monadic]] blocks. It is impossible
- * to enable both DSLs in one function.
+ * [[https://github.com/ThoughtWorksInc/Binding.scala Binding.scala]] is used to create reactive HTML templating
+ * from ordinary Scala control flow.
+ *
+ * == Delimited continuations ==
+ *
+ * Another generic interface of control flow is continuation,
+ * which is known as
+ * [[https://www.schoolofhaskell.com/user/dpiponi/the-mother-of-all-monads the mother of all monads]],
+ * where control flows in specific domain can be supported by specific final result types of continuations.
+ *
+ * [[https://github.com/scala/scala-continuations scala-continuations]]
+ * and [[https://github.com/qifun/stateless-future stateless-future]]
+ * are two delimited continuation implementations.
+ * Both projects can convert ordinary control flow to continuation-passing style closure chains at compiler time.
+ *
+ * For example, [[https://github.com/qifun/stateless-future-akka stateless-future-akka]],
+ * based on `stateless-future`,
+ * provides a special final result type for akka actors.
+ * Unlike [[akka.actor.FSM]]'s inconsistent control flows, users can create complex finite-state machines
+ * from simple ordinary control flows along with `stateless-future-akka`'s domain-specific keyword `nextMessage`.
+ *
+ * == Collaborative DSLs ==
+ *
+ * All the above approaches lack of the ability to collaborate with other DSLs.
+ * Each of the above DSLs can be only exclusively enabled in a code block.
+ * For example,
+ * [[https://github.com/scala/scala-continuations scala-continuations]]
+ * enables calls to `@cps` method in [[scala.util.continuations.reset]] blocks,
+ * and [[https://github.com/ThoughtWorksInc/each ThoughtWorks Each]]
+ * enables the magic `each` method for [[scalaz.Monad]] in [[com.thoughtworks.each.Monadic.monadic]] blocks.
+ * It is impossible to enable both DSLs in one function.
*
* This [[https://github.com/ThoughtWorksInc/Dsl.scala Dsl.scala]] project resolves this problem.
*
- * We also provide adapters to all the above kinds of DSLs. Instead of switching different DSLs between different
- * functions, DSL users can use multiple DSLs together in one function, by simply adding
- * [[com.thoughtworks.dsl.compilerplugins.BangNotation our Scala compiler plug-in]].
- *
- * @example
- * Suppose you want to create an [[https://en.wikipedia.org/wiki/Xorshift Xorshift]] random number generator.
- *
- * The generated numbers should be stored in a lazily evaluated infinite [[scala.collection.immutable.Stream Stream]],
- * which can be implemented as a recursive function that produce the next random number in each iteration, with the
- * help of our built-in domain-specific keyword [[com.thoughtworks.dsl.keywords.Yield Yield]].
- *
- * {{{
- * import com.thoughtworks.dsl.Dsl.reset
- * import com.thoughtworks.dsl.keywords.Yield
- *
- * def xorshiftRandomGenerator(seed: Int): Stream[Int] = {
- * val tmp1 = seed ^ (seed << 13)
- * val tmp2 = tmp1 ^ (tmp1 >>> 17)
- * val tmp3 = tmp2 ^ (tmp2 << 5)
- * !Yield(tmp3)
- * xorshiftRandomGenerator(tmp3)
- * }: @reset
- *
- * val myGenerator = xorshiftRandomGenerator(seed = 123)
- *
- * myGenerator(0) should be(31682556)
- * myGenerator(1) should be(-276305998)
- * myGenerator(2) should be(2101636938)
- * }}}
- *
- * [[com.thoughtworks.dsl.keywords.Yield Yield]] is an keyword to produce a value for a lazily evaluated
- * [[scala.collection.immutable.Stream Stream]]. That is to say, [[scala.collection.immutable.Stream Stream]] is the
- * domain where the DSL [[com.thoughtworks.dsl.keywords.Yield Yield]] can be used, which was interpreted like the
- * `yield` keyword in C#, JavaScript or Python.
- *
- * Note that the body of `xorshiftRandomGenerator` is annotated as `@[[Dsl.reset reset]]`, which enables the
- * [[Dsl.Keyword#unary_$bang !-notation]] in the code block.
- *
- * Alternatively, you can also use the [[com.thoughtworks.dsl.compilerplugins.ResetEverywhere ResetEverywhere]]
- * compiler plug-in, which enable [[Dsl.Keyword#unary_$bang !-notation]] for every methods and functions.
- * @example
- * [[com.thoughtworks.dsl.keywords.Yield Yield]] and [[scala.collection.immutable.Stream Stream]] can be also used
- * for logging.
- *
- * Suppose you have a function to parse an JSON file, you can append log records to a
- * [[scala.collection.immutable.Stream Stream]] during parsing.
- *
- * {{{
- * import com.thoughtworks.dsl.keywords.Yield
- * import com.thoughtworks.dsl.Dsl.!!
- * import scala.util.parsing.json._
- * def parseAndLog1(jsonContent: String, defaultValue: JSONType): Stream[String] !! JSONType = { (callback: JSONType => Stream[String]) =>
- * !Yield(s"I am going to parse the JSON text $jsonContent...")
- * JSON.parseRaw(jsonContent) match {
- * case Some(json) =>
- * !Yield(s"Succeeded to parse $jsonContent")
- * callback(json)
- * case None =>
- * !Yield(s"Failed to parse $jsonContent")
- * callback(defaultValue)
- * }
- * }
- * }}}
- *
- * Since the function produces both a [[scala.util.parsing.json.JSONType JSONType]] and a
- * [[scala.collection.immutable.Stream Stream]] of logs, the return type is now `Stream[String] !! JSONType`, where
- * [[com.thoughtworks.dsl.Dsl.$bang$bang !!]] is `(JSONType => Stream[String]) => Stream[String]`, an alias of
- * continuation-passing style function, indicating it produces both a [[scala.util.parsing.json.JSONType JSONType]] and
- * a [[scala.Stream Stream]] of logs.
- *
- * {{{
- * val logs = parseAndLog1(""" { "key": "value" } """, JSONArray(Nil)) { json =>
- * json should be(JSONObject(Map("key" -> "value")))
- * Stream("done")
- * }
- *
- * logs should be(Stream("I am going to parse the JSON text { \"key\": \"value\" } ...",
- * "Succeeded to parse { \"key\": \"value\" } ",
- * "done"))
- * }}}
- * @example
- * The closure in the previous example can be simplified with the help of Scala's placeholder syntax:
- *
- * {{{
- * import com.thoughtworks.dsl.keywords.Yield
- * import com.thoughtworks.dsl.Dsl.!!
- * import scala.util.parsing.json._
- * def parseAndLog2(jsonContent: String, defaultValue: JSONType): Stream[String] !! JSONType = _ {
- * !Yield(s"I am going to parse the JSON text $jsonContent...")
- * JSON.parseRaw(jsonContent) match {
- * case Some(json) =>
- * !Yield(s"Succeeded to parse $jsonContent")
- * json
- * case None =>
- * !Yield(s"Failed to parse $jsonContent")
- * defaultValue
- * }
- * }
- *
- * val logs = parseAndLog2(""" { "key": "value" } """, JSONArray(Nil)) { json =>
- * json should be(JSONObject(Map("key" -> "value")))
- * Stream("done")
- * }
- *
- * logs should be(Stream("I am going to parse the JSON text { \"key\": \"value\" } ...",
- * "Succeeded to parse { \"key\": \"value\" } ",
- * "done"))
- * }}}
- *
- * Note that `parseAndLog2` is equivelent to `parseAndLog1`. The code block after underscore is still inside a function
- * whose return type is `Stream[String]`.
- * @example
- * Instead of manually create the continuation-passing style function, you can also create the function from a
- * [[com.thoughtworks.dsl.Dsl.$bang$bang !!]] block.
- *
- * {{{
- * import com.thoughtworks.dsl.keywords.Yield
- * import com.thoughtworks.dsl.Dsl.!!
- * import scala.util.parsing.json._
- * def parseAndLog3(jsonContent: String, defaultValue: JSONType): Stream[String] !! JSONType = !! {
- * !Yield(s"I am going to parse the JSON text $jsonContent...")
- * JSON.parseRaw(jsonContent) match {
- * case Some(json) =>
- * !Yield(s"Succeeded to parse $jsonContent")
- * json
- * case None =>
- * !Yield(s"Failed to parse $jsonContent")
- * defaultValue
- * }
- * }
- *
- * val logs = parseAndLog3(""" { "key": "value" } """, JSONArray(Nil)) { json =>
- * json should be(JSONObject(Map("key" -> "value")))
- * Stream("done")
- * }
- *
- * logs should be(Stream("I am going to parse the JSON text { \"key\": \"value\" } ...",
- * "Succeeded to parse { \"key\": \"value\" } ",
- * "done"))
- * }}}
- *
- * Unlike the `parseAndLog2` example, The code inside a `!!` block is not in an anonymous function. Instead, they are
- * directly inside `parseAndLog3`, whose return type is `Stream[String] !! JSONType`.
- *
- * That is to say, the domain of those [[com.thoughtworks.dsl.keywords.Yield Yield]] keywords in `parseAndLog3` is not
- * `Stream[String]` any more, the domain is `Stream[String] !! JSONType` now, which supports more keywords, which you
- * will learnt from the next examples, than the `Stream[String]` domain.
- * @example
- * [[com.thoughtworks.dsl.Dsl.$bang$bang !!]], or [[com.thoughtworks.dsl.Dsl.Continuation Continuation]], is the
- * preferred approach to enable multiple domains in one function.
- *
- * For example, you can create a function that lazily read each line of a [[java.io.BufferedReader BufferedReader]] to
- * a [[scala.Stream Stream]], automatically close the [[java.io.BufferedReader BufferedReader]] after reading the last
- * line, and finally return the total number of lines in the `Stream[String] !! Throwable !! Int` domain.
- *
- * {{{
- * import com.thoughtworks.dsl.Dsl.!!
- * import com.thoughtworks.dsl.keywords.Using
- * import com.thoughtworks.dsl.keywords.Yield
- * import com.thoughtworks.dsl.keywords.Shift._
- * import java.io._
- *
- * def readerToStream(createReader: () => BufferedReader): Stream[String] !! Throwable !! Int = !! {
- * val reader = !Using(createReader())
- *
- * def loop(lineNumber: Int): Stream[String] !! Throwable !! Int = _ {
- * reader.readLine() match {
- * case null =>
- * lineNumber
- * case line =>
- * !Yield(line)
- * !loop(lineNumber + 1)
- * }
- * }
- *
- * !loop(0)
- * }
- * }}}
- *
- * `!loop(0)` is a shortcut of `!Shift(loop(0))`, because there is
- * [[keywords.Shift.implicitShift an implicit conversion]] from `Stream[String] !! Throwable !! Int` to a
- * [[keywords.Shift]] case class, which is similar to the `await` keyword in JavaScript, Python or C#.
- *
- * A type like `A !! B !! C` means a domain-specific value of type `C` in the domain of `A` and `B`. When `B` is
- * [[scala.Throwable Throwable]], the [[keywords.Using]] is available, which will close a resource when exiting the
- * current function.
- *
- * {{{
- * import scala.util.Success
- *
- * var isClosed = false
- * def createReader() = {
- * new BufferedReader(new StringReader("line1\nline2\nline3")) {
- * override def close() = {
- * isClosed = true
- * }
- * }
- * }
- *
- * val stream = readerToStream(createReader _) { numberOfLines: Int =>
- * numberOfLines should be(3)
- *
- * Function.const(Stream.empty)(_)
- * } { e: Throwable =>
- * throw new AssertionError("Unexpected exception during readerToStream", e)
- * }
- *
- * isClosed should be(false)
- * stream should be(Stream("line1", "line2", "line3"))
- * isClosed should be(true)
- * }}}
- *
- * @example
- * `try` / `catch` / `finally` expressions are also supported in functions that return `Stream[String] !! Throwable`,
- * because of the [[scala.Throwable]] part in the signature.
- *
- * {{{
- * import com.thoughtworks.dsl.Dsl.!!, keywords._
- * var finallyBlockInvoked = 0
- * class MyException extends Exception
- * def f: Stream[String] !! Throwable = {
- * while (true) {
- * try {
- * !new Yield("yield value")
- * if (true) {
- * throw new MyException
- * }
- * } catch {
- * case e: RuntimeException =>
- * throw new AssertionError("Should not catch an RuntimeException", e)
- * } finally {
- * finallyBlockInvoked += 1
- * }
- * }
- * throw new AssertionError("Unreachable code")
- * }
- *
- * val result = f { e =>
- * e should be(a[MyException])
- * Stream.empty
- * }
- * result should be(Stream("yield value"))
- * finallyBlockInvoked should be(1)
- * }}}
- * @example
- * If you don't need to collaborate to [[scala.Stream Stream]] or other domains, you can use `TailRec[Unit] !!
- * Throwable !! A` or the alias [[domains.task.Task]] as the return type, which can be used as an asynchronous task
- * that support RAII, as a higher-performance replacement of [[scala.concurrent.Future]], [[scalaz.concurrent.Task]]
- * or [[monix.eval.Task]].
- *
- * Also, there are some keywords in [[keywords.AsynchronousIo]] to asynchronously perform Java NIO.2 IO operations in a
- * [[domains.task.Task]] domain. For example, you can implement an HTTP client from those keywords.
- *
- * {{{
- * import com.thoughtworks.dsl.domains.task._
- * import com.thoughtworks.dsl.keywords._
- * import com.thoughtworks.dsl.keywords.Shift.implicitShift
- * import com.thoughtworks.dsl.keywords.AsynchronousIo._
- * import java.io._
- * import java.net._
- * import java.nio._, channels._
- *
- * def readAll(channel: AsynchronousByteChannel, destination: ByteBuffer): Task[Unit] = _ {
- * if (destination.remaining > 0) {
- * val numberOfBytesRead: Int = !Read(channel, destination)
- * numberOfBytesRead match {
- * case -1 =>
- * case _ => !readAll(channel, destination)
- * }
- * } else {
- * throw new IOException("The response is too big to read.")
- * }
- * }
- *
- * def writeAll[Domain](channel: AsynchronousByteChannel, destination: ByteBuffer): Task[Unit] = _ {
- * while (destination.remaining > 0) {
- * !Write(channel, destination)
- * }
- * }
- *
- * def httpClient(url: URL): Task[String] = _ {
- * val socket = AsynchronousSocketChannel.open()
- * try {
- * val port = if (url.getPort == -1) 80 else url.getPort
- * val address = new InetSocketAddress(url.getHost, port)
- * !AsynchronousIo.Connect(socket, address)
- * val request = ByteBuffer.wrap(s"GET ${url.getPath} HTTP/1.1\r\nHost:${url.getHost}\r\nConnection:Close\r\n\r\n".getBytes)
- * !writeAll(socket, request)
- * val response = ByteBuffer.allocate(100000)
- * !readAll(socket, response)
- * response.flip()
- * io.Codec.UTF8.decoder.decode(response).toString
- * } finally {
- * socket.close()
- * }
- * }
- * }}}
- *
- * The usage of `Task` is similar to previous examples. You can check the result or exception in asynchronous handlers.
- *
- * But we also provides [[com.thoughtworks.dsl.domains.task.Task.blockingAwait blockingAwait]] and some other utilities
- * at [[domains.task.Task]].
- *
- * {{{
- * import com.thoughtworks.dsl.domains.task.Task.blockingAwait
- *
- * val url = new URL("http://localhost:8085/ping")
- * val fileContent = blockingAwait(httpClient(url))
- * fileContent should startWith("HTTP/1.1 200 OK")
- * }}}
- *
- * Another useful keyword for asynchronous programming is [[com.thoughtworks.dsl.keywords.Fork Fork]], which duplicate
- * the current control flow, and the child control flows are executed in parallel, similar to the POSIX `fork` system
- * call.
- *
- * [[com.thoughtworks.dsl.keywords.Fork Fork]] should be used inside a [[com.thoughtworks.dsl.domains.task.Task#join]]
- * block, which collects the result of each forked control flow.
- *
- * {{{
- * import com.thoughtworks.dsl.keywords.Fork
- * import com.thoughtworks.dsl.keywords.Return
- * val Urls = Seq(
- * new URL("http://localhost:8085/ping"),
- * new URL("http://localhost:8085/pong")
- * )
- * def parallelTask: Task[Seq[String]] = {
- * val url = !Fork(Urls)
- * !Return(!httpClient(url))
- * }
- *
- * inside(blockingAwait(parallelTask)) {
- * case Seq(fileContent0, fileContent1) =>
- * fileContent0 should startWith("HTTP/1.1 200 OK")
- * fileContent1 should startWith("HTTP/1.1 200 OK")
- * }
- * }}}
- * @example
- * The built-in [[keywords.Monadic]] can be used as an adaptor to [[scalaz.Monad]] and [[scalaz.MonadTrans]], to
- * create monadic code from imperative syntax, similar to the !-notation in Idris.
- *
- * For example, suppose you are creating a program that counts lines of code under a directory. You want to store the
- * result in a [[scala.Stream Stream]] of line count of each file.
- *
- * {{{
- * import java.io.File
- * import com.thoughtworks.dsl.keywords.Monadic
- * import com.thoughtworks.dsl.domains.scalaz._
- * import scalaz.std.stream._
- *
- * def countMonadic(file: File): Stream[Int] = Stream {
- * if (file.isDirectory) {
- * file.listFiles() match {
- * case null =>
- * // Unable to open `file`
- * !Monadic(Stream.empty[Int])
- * case children =>
- * // Import this implicit conversion to omit the Monadic keyword
- * import com.thoughtworks.dsl.keywords.Monadic.implicitMonadic
- *
- * val child: File = !children.toStream
- * !countMonadic(child)
- * }
- * } else {
- * scala.io.Source.fromFile(file).getLines.size
- * }
- * }
- *
- * val countCurrentSourceFile = countMonadic(new File(sourcecode.File()))
- *
- * inside(countCurrentSourceFile) {
- * case Stream(lineCount) =>
- * lineCount should be > 0
- * }
- *
- * }}}
- * @example
- * The previous code requires a `toStream` conversion on `children`, because `children`'s type `Array[File]` does not
- * fit the `F` type parameter in [[scalaz.Monad.bind]].
- *
- * The conversion can be avoided if using [[scala.collection.generic.CanBuildFrom CanBuildFrom]] type class instead of
- * monads.
- *
- * We provide a [[com.thoughtworks.dsl.keywords.Each Each]] keyword to extract each element in a Scala collection. The
- * behavior is similar to monad, except the collection type can vary.
- *
- * For example, you can extract each element from an [[scala.Array Array]], even when the return type (or the domain)
- * is a [[scala.collection.immutable.Stream Stream]].
- *
- * {{{
- * import java.io.File
- * import com.thoughtworks.dsl.keywords.Monadic, Monadic.implicitMonadic
- * import com.thoughtworks.dsl.keywords.Each
- * import com.thoughtworks.dsl.domains.scalaz._
- * import scalaz.std.stream._
- *
- * def countEach(file: File): Stream[Int] = Stream {
- * if (file.isDirectory) {
- * file.listFiles() match {
- * case null =>
- * // Unable to open `file`
- * !Stream.empty[Int]
- * case children =>
- * val child: File = !Each(children)
- * !countEach(child)
- * }
- * } else {
- * scala.io.Source.fromFile(file).getLines.size
- * }
- * }
- *
- * val countCurrentSourceFile = countEach(new File(sourcecode.File()))
- *
- * inside(countCurrentSourceFile) {
- * case Stream(lineCount) =>
- * lineCount should be > 0
- * }
- *
- * }}}
- *
- * Unlike Haskell's do-notation or Idris's !-notation, Dsl.scala allows non-monadic keywords like
- * [[com.thoughtworks.dsl.keywords.Each Each]] works along with monads.
- * @example
- * Dsl.scala also supports [[scalaz.MonadTrans]].
- *
- * Considering the line counter implemented in previous example may be failed for some files, due to permission issue
- * or other IO problem, you can use [[scalaz.OptionT]] monad transformer to mark those failed file as a
- * [[scala.None None]].
- *
- * {{{
- * import scalaz._
- * import java.io.File
- * import com.thoughtworks.dsl.keywords.Monadic, Monadic.implicitMonadic
- * import com.thoughtworks.dsl.domains.scalaz._
- * import scalaz.std.stream._
- *
- * def countLift(file: File): OptionT[Stream, Int] = OptionT.some {
- * if (file.isDirectory) {
- * file.listFiles() match {
- * case null =>
- * // Unable to open `file`
- * !OptionT.none[Stream, Int]
- * case children =>
- * val child: File = !Stream(children: _*)
- * !countLift(child)
- * }
- * } else {
- * scala.io.Source.fromFile(file).getLines.size
- * }
- * }
- *
- * val countCurrentSourceFile = countLift(new File(sourcecode.File())).run
- *
- * inside(countCurrentSourceFile) {
- * case Stream(Some(lineCount)) =>
- * lineCount should be > 0
- * }
- * }}}
- *
- * Note that our keywords are adaptive to the domain it belongs to. Thus, instead of explicit
- * `!Monadic(OptionT.optionTMonadTrans.liftM(Stream(children: _*)))`, you can simply have `!Stream(children: _*)`. The
- * implicit lifting feature looks like Idris's effect monads, though the mechanisms is different from `implicit lift`
- * in Idris.
- * @see
- * [[Dsl]] for the guideline to create your custom DSL.
- * @see
- * [[domains.scalaz]] for using [[Dsl.Keyword#unary_$bang !-notation]] with [[scalaz]].
- * @see
- * [[domains.cats]] for using [[Dsl.Keyword#unary_$bang !-notation]] with [[cats]].
+ * We also provide adapters to all the above kinds of DSLs.
+ * Instead of switching different DSLs between different functions,
+ * DSL users can use multiple DSLs together in one function,
+ * by simply adding [[com.thoughtworks.dsl.compilerplugins.BangNotation our Scala compiler plug-in]].
+ *
+ * @example Suppose you want to create an [[https://en.wikipedia.org/wiki/Xorshift Xorshift]] random number generator.
+ *
+ * The generated numbers should be stored in a lazily evaluated infinite [[scala.collection.immutable.Stream Stream]],
+ * which can be implemented as a recursive function that produce the next random number in each iteration,
+ * with the help of our built-in domain-specific keyword [[com.thoughtworks.dsl.keywords.Yield Yield]].
+ *
+ * {{{
+ * import com.thoughtworks.dsl.Dsl.reset
+ * import com.thoughtworks.dsl.keywords.Yield
+ *
+ * def xorshiftRandomGenerator(seed: Int): Stream[Int] = {
+ * val tmp1 = seed ^ (seed << 13)
+ * val tmp2 = tmp1 ^ (tmp1 >>> 17)
+ * val tmp3 = tmp2 ^ (tmp2 << 5)
+ * !Yield(tmp3)
+ * xorshiftRandomGenerator(tmp3)
+ * }: @reset
+ *
+ * val myGenerator = xorshiftRandomGenerator(seed = 123)
+ *
+ * myGenerator(0) should be(31682556)
+ * myGenerator(1) should be(-276305998)
+ * myGenerator(2) should be(2101636938)
+ * }}}
+ *
+ * [[com.thoughtworks.dsl.keywords.Yield Yield]] is an keyword to produce a value
+ * for a lazily evaluated [[scala.collection.immutable.Stream Stream]].
+ * That is to say, [[scala.collection.immutable.Stream Stream]] is the domain
+ * where the DSL [[com.thoughtworks.dsl.keywords.Yield Yield]] can be used,
+ * which was interpreted like the `yield` keyword in C#, JavaScript or Python.
+ *
+ * Note that the body of `xorshiftRandomGenerator` is annotated as `@[[Dsl.reset reset]]`,
+ * which enables the [[Dsl.Keyword#unary_$bang !-notation]] in the code block.
+ *
+ * Alternatively, you can also use the
+ * [[com.thoughtworks.dsl.compilerplugins.ResetEverywhere ResetEverywhere]] compiler plug-in,
+ * which enable [[Dsl.Keyword#unary_$bang !-notation]] for every methods and functions.
+ * @example [[com.thoughtworks.dsl.keywords.Yield Yield]] and [[scala.collection.immutable.Stream Stream]]
+ * can be also used for logging.
+ *
+ * Suppose you have a function to parse an JSON file,
+ * you can append log records to a [[scala.collection.immutable.Stream Stream]] during parsing.
+ *
+ * {{{
+ * import com.thoughtworks.dsl.keywords.Yield
+ * import com.thoughtworks.dsl.Dsl.!!
+ * import scala.util.parsing.json._
+ * def parseAndLog1(jsonContent: String, defaultValue: JSONType): Stream[String] !! JSONType = { (callback: JSONType => Stream[String]) =>
+ * !Yield(s"I am going to parse the JSON text $jsonContent...")
+ * JSON.parseRaw(jsonContent) match {
+ * case Some(json) =>
+ * !Yield(s"Succeeded to parse $jsonContent")
+ * callback(json)
+ * case None =>
+ * !Yield(s"Failed to parse $jsonContent")
+ * callback(defaultValue)
+ * }
+ * }
+ * }}}
+ *
+ * Since the function produces both a [[scala.util.parsing.json.JSONType JSONType]]
+ * and a [[scala.collection.immutable.Stream Stream]] of logs,
+ * the return type is now `Stream[String] !! JSONType`,
+ * where [[com.thoughtworks.dsl.Dsl.$bang$bang !!]] is
+ * `(JSONType => Stream[String]) => Stream[String]`,
+ * an alias of continuation-passing style function,
+ * indicating it produces both a [[scala.util.parsing.json.JSONType JSONType]] and a [[scala.Stream Stream]] of logs.
+ *
+ * {{{
+ * val logs = parseAndLog1(""" { "key": "value" } """, JSONArray(Nil)) { json =>
+ * json should be(JSONObject(Map("key" -> "value")))
+ * Stream("done")
+ * }
+ *
+ * logs should be(Stream("I am going to parse the JSON text { \"key\": \"value\" } ...",
+ * "Succeeded to parse { \"key\": \"value\" } ",
+ * "done"))
+ * }}}
+ * @example The closure in the previous example can be simplified with the help of Scala's placeholder syntax:
+ *
+ * {{{
+ * import com.thoughtworks.dsl.keywords.Yield
+ * import com.thoughtworks.dsl.Dsl.!!
+ * import scala.util.parsing.json._
+ * def parseAndLog2(jsonContent: String, defaultValue: JSONType): Stream[String] !! JSONType = _ {
+ * !Yield(s"I am going to parse the JSON text $jsonContent...")
+ * JSON.parseRaw(jsonContent) match {
+ * case Some(json) =>
+ * !Yield(s"Succeeded to parse $jsonContent")
+ * json
+ * case None =>
+ * !Yield(s"Failed to parse $jsonContent")
+ * defaultValue
+ * }
+ * }
+ *
+ * val logs = parseAndLog2(""" { "key": "value" } """, JSONArray(Nil)) { json =>
+ * json should be(JSONObject(Map("key" -> "value")))
+ * Stream("done")
+ * }
+ *
+ * logs should be(Stream("I am going to parse the JSON text { \"key\": \"value\" } ...",
+ * "Succeeded to parse { \"key\": \"value\" } ",
+ * "done"))
+ * }}}
+ *
+ * Note that `parseAndLog2` is equivelent to `parseAndLog1`.
+ * The code block after underscore is still inside a function whose return type is `Stream[String]`.
+ * @example Instead of manually create the continuation-passing style function,
+ * you can also create the function from a [[com.thoughtworks.dsl.Dsl.$bang$bang !!]] block.
+ *
+ * {{{
+ * import com.thoughtworks.dsl.keywords.Yield
+ * import com.thoughtworks.dsl.Dsl.!!
+ * import scala.util.parsing.json._
+ * def parseAndLog3(jsonContent: String, defaultValue: JSONType): Stream[String] !! JSONType = !! {
+ * !Yield(s"I am going to parse the JSON text $jsonContent...")
+ * JSON.parseRaw(jsonContent) match {
+ * case Some(json) =>
+ * !Yield(s"Succeeded to parse $jsonContent")
+ * json
+ * case None =>
+ * !Yield(s"Failed to parse $jsonContent")
+ * defaultValue
+ * }
+ * }
+ *
+ * val logs = parseAndLog3(""" { "key": "value" } """, JSONArray(Nil)) { json =>
+ * json should be(JSONObject(Map("key" -> "value")))
+ * Stream("done")
+ * }
+ *
+ * logs should be(Stream("I am going to parse the JSON text { \"key\": \"value\" } ...",
+ * "Succeeded to parse { \"key\": \"value\" } ",
+ * "done"))
+ * }}}
+ *
+ * Unlike the `parseAndLog2` example, The code inside a `!!` block is not in an anonymous function.
+ * Instead, they are directly inside `parseAndLog3`, whose return type is `Stream[String] !! JSONType`.
+ *
+ * That is to say,
+ * the domain of those [[com.thoughtworks.dsl.keywords.Yield Yield]] keywords in `parseAndLog3`
+ * is not `Stream[String]` any more, the domain is `Stream[String] !! JSONType` now,
+ * which supports more keywords, which you will learnt from the next examples,
+ * than the `Stream[String]` domain.
+ * @example [[com.thoughtworks.dsl.Dsl.$bang$bang !!]], or [[com.thoughtworks.dsl.Dsl.Continuation Continuation]],
+ * is the preferred approach to enable multiple domains in one function.
+ *
+ * For example, you can create a function that
+ * lazily read each line of a [[java.io.BufferedReader BufferedReader]] to a [[scala.Stream Stream]],
+ * automatically close the [[java.io.BufferedReader BufferedReader]] after reading the last line,
+ * and finally return the total number of lines in the `Stream[String] !! Throwable !! Int` domain.
+ *
+ * {{{
+ * import com.thoughtworks.dsl.Dsl.!!
+ * import com.thoughtworks.dsl.keywords.Using
+ * import com.thoughtworks.dsl.keywords.Yield
+ * import com.thoughtworks.dsl.keywords.Shift._
+ * import java.io._
+ *
+ * def readerToStream(createReader: () => BufferedReader): Stream[String] !! Throwable !! Int = !! {
+ * val reader = !Using(createReader())
+ *
+ * def loop(lineNumber: Int): Stream[String] !! Throwable !! Int = _ {
+ * reader.readLine() match {
+ * case null =>
+ * lineNumber
+ * case line =>
+ * !Yield(line)
+ * !loop(lineNumber + 1)
+ * }
+ * }
+ *
+ * !loop(0)
+ * }
+ * }}}
+ *
+ * `!loop(0)` is a shortcut of `!Shift(loop(0))`,
+ * because there is [[keywords.Shift.implicitShift an implicit conversion]]
+ * from `Stream[String] !! Throwable !! Int` to a [[keywords.Shift]] case class,
+ * which is similar to the `await` keyword in JavaScript, Python or C#.
+ *
+ * A type like `A !! B !! C` means a domain-specific value of type `C` in the domain of `A` and `B`.
+ * When `B` is [[scala.Throwable Throwable]], the [[keywords.Using]]
+ * is available, which will close a resource when exiting the current function.
+ *
+ * {{{
+ * import scala.util.Success
+ *
+ * var isClosed = false
+ * def createReader() = {
+ * new BufferedReader(new StringReader("line1\nline2\nline3")) {
+ * override def close() = {
+ * isClosed = true
+ * }
+ * }
+ * }
+ *
+ * val stream = readerToStream(createReader _) { numberOfLines: Int =>
+ * numberOfLines should be(3)
+ *
+ * Function.const(Stream.empty)(_)
+ * } { e: Throwable =>
+ * throw new AssertionError("Unexpected exception during readerToStream", e)
+ * }
+ *
+ * isClosed should be(false)
+ * stream should be(Stream("line1", "line2", "line3"))
+ * isClosed should be(true)
+ * }}}
+ *
+ * @example `try` / `catch` / `finally` expressions are also supported in functions that return `Stream[String] !! Throwable`,
+ * because of the [[scala.Throwable]] part in the signature.
+ *
+ * {{{
+ * import com.thoughtworks.dsl.Dsl.!!, keywords._
+ * var finallyBlockInvoked = 0
+ * class MyException extends Exception
+ * def f: Stream[String] !! Throwable = {
+ * while (true) {
+ * try {
+ * !new Yield("yield value")
+ * if (true) {
+ * throw new MyException
+ * }
+ * } catch {
+ * case e: RuntimeException =>
+ * throw new AssertionError("Should not catch an RuntimeException", e)
+ * } finally {
+ * finallyBlockInvoked += 1
+ * }
+ * }
+ * throw new AssertionError("Unreachable code")
+ * }
+ *
+ * val result = f { e =>
+ * e should be(a[MyException])
+ * Stream.empty
+ * }
+ * result should be(Stream("yield value"))
+ * finallyBlockInvoked should be(1)
+ * }}}
+ * @example If you don't need to collaborate to [[scala.Stream Stream]] or other domains,
+ * you can use `TailRec[Unit] !! Throwable !! A`
+ * or the alias [[domains.task.Task]] as the return type,
+ * which can be used as an asynchronous task that support RAII,
+ * as a higher-performance replacement of
+ * [[scala.concurrent.Future]], [[scalaz.concurrent.Task]] or [[monix.eval.Task]].
+ *
+ * Also, there are some keywords in [[keywords.AsynchronousIo]]
+ * to asynchronously perform Java NIO.2 IO operations in a [[domains.task.Task]] domain.
+ * For example, you can implement an HTTP client from those keywords.
+ *
+ * {{{
+ * import com.thoughtworks.dsl.domains.task._
+ * import com.thoughtworks.dsl.keywords._
+ * import com.thoughtworks.dsl.keywords.Shift.implicitShift
+ * import com.thoughtworks.dsl.keywords.AsynchronousIo._
+ * import java.io._
+ * import java.net._
+ * import java.nio._, channels._
+ *
+ * def readAll(channel: AsynchronousByteChannel, destination: ByteBuffer): Task[Unit] = _ {
+ * if (destination.remaining > 0) {
+ * val numberOfBytesRead: Int = !Read(channel, destination)
+ * numberOfBytesRead match {
+ * case -1 =>
+ * case _ => !readAll(channel, destination)
+ * }
+ * } else {
+ * throw new IOException("The response is too big to read.")
+ * }
+ * }
+ *
+ * def writeAll[Domain](channel: AsynchronousByteChannel, destination: ByteBuffer): Task[Unit] = _ {
+ * while (destination.remaining > 0) {
+ * !Write(channel, destination)
+ * }
+ * }
+ *
+ * def httpClient(url: URL): Task[String] = _ {
+ * val socket = AsynchronousSocketChannel.open()
+ * try {
+ * val port = if (url.getPort == -1) 80 else url.getPort
+ * val address = new InetSocketAddress(url.getHost, port)
+ * !AsynchronousIo.Connect(socket, address)
+ * val request = ByteBuffer.wrap(s"GET ${url.getPath} HTTP/1.1\r\nHost:${url.getHost}\r\nConnection:Close\r\n\r\n".getBytes)
+ * !writeAll(socket, request)
+ * val response = ByteBuffer.allocate(100000)
+ * !readAll(socket, response)
+ * response.flip()
+ * io.Codec.UTF8.decoder.decode(response).toString
+ * } finally {
+ * socket.close()
+ * }
+ * }
+ * }}}
+ *
+ * The usage of `Task` is similar to previous examples.
+ * You can check the result or exception in asynchronous handlers.
+ *
+ * But we also provides [[com.thoughtworks.dsl.domains.task.Task.blockingAwait blockingAwait]] and some other utilities
+ * at [[domains.task.Task]].
+ *
+ * {{{
+ * import com.thoughtworks.dsl.domains.task.Task.blockingAwait
+ *
+ * val url = new URL("http://localhost:8085/ping")
+ * val fileContent = blockingAwait(httpClient(url))
+ * fileContent should startWith("HTTP/1.1 200 OK")
+ * }}}
+ *
+ * Another useful keyword for asynchronous programming is [[com.thoughtworks.dsl.keywords.Fork Fork]],
+ * which duplicate the current control flow, and the child control flows are executed in parallel,
+ * similar to the POSIX `fork` system call.
+ *
+ * [[com.thoughtworks.dsl.keywords.Fork Fork]] should be used inside
+ * a [[com.thoughtworks.dsl.domains.task.Task#join]] block, which collects the result of each forked control flow.
+ *
+ * {{{
+ * import com.thoughtworks.dsl.keywords.Fork
+ * import com.thoughtworks.dsl.keywords.Return
+ * val Urls = Seq(
+ * new URL("http://localhost:8085/ping"),
+ * new URL("http://localhost:8085/pong")
+ * )
+ * def parallelTask: Task[Seq[String]] = {
+ * val url = !Fork(Urls)
+ * !Return(!httpClient(url))
+ * }
+ *
+ * inside(blockingAwait(parallelTask)) {
+ * case Seq(fileContent0, fileContent1) =>
+ * fileContent0 should startWith("HTTP/1.1 200 OK")
+ * fileContent1 should startWith("HTTP/1.1 200 OK")
+ * }
+ * }}}
+ * @example The built-in [[keywords.Monadic]] can be used as an adaptor
+ * to [[scalaz.Monad]] and [[scalaz.MonadTrans]],
+ * to create monadic code from imperative syntax,
+ * similar to the !-notation in Idris.
+ *
+ * For example, suppose you are creating a program that counts lines of code under a directory.
+ * You want to store the result in a [[scala.Stream Stream]] of line count of each file.
+ *
+ * {{{
+ * import java.io.File
+ * import com.thoughtworks.dsl.keywords.Monadic
+ * import com.thoughtworks.dsl.domains.scalaz._
+ * import scalaz.std.stream._
+ *
+ * def countMonadic(file: File): Stream[Int] = Stream {
+ * if (file.isDirectory) {
+ * file.listFiles() match {
+ * case null =>
+ * // Unable to open `file`
+ * !Monadic(Stream.empty[Int])
+ * case children =>
+ * // Import this implicit conversion to omit the Monadic keyword
+ * import com.thoughtworks.dsl.keywords.Monadic.implicitMonadic
+ *
+ * val child: File = !children.toStream
+ * !countMonadic(child)
+ * }
+ * } else {
+ * scala.io.Source.fromFile(file).getLines.size
+ * }
+ * }
+ *
+ * val countCurrentSourceFile = countMonadic(new File(sourcecode.File()))
+ *
+ * inside(countCurrentSourceFile) {
+ * case Stream(lineCount) =>
+ * lineCount should be > 0
+ * }
+ *
+ * }}}
+ * @example The previous code requires a `toStream` conversion on `children`,
+ * because `children`'s type `Array[File]` does not fit the `F` type parameter in [[scalaz.Monad.bind]].
+ *
+ * The conversion can be avoided if using [[scala.collection.generic.CanBuildFrom CanBuildFrom]] type class
+ * instead of monads.
+ *
+ * We provide a [[com.thoughtworks.dsl.keywords.Each Each]]
+ * keyword to extract each element in a Scala collection.
+ * The behavior is similar to monad, except the collection type can vary.
+ *
+ * For example, you can extract each element from an [[scala.Array Array]],
+ * even when the return type (or the domain) is a [[scala.collection.immutable.Stream Stream]].
+ *
+ * {{{
+ * import java.io.File
+ * import com.thoughtworks.dsl.keywords.Monadic, Monadic.implicitMonadic
+ * import com.thoughtworks.dsl.keywords.Each
+ * import com.thoughtworks.dsl.domains.scalaz._
+ * import scalaz.std.stream._
+ *
+ * def countEach(file: File): Stream[Int] = Stream {
+ * if (file.isDirectory) {
+ * file.listFiles() match {
+ * case null =>
+ * // Unable to open `file`
+ * !Stream.empty[Int]
+ * case children =>
+ * val child: File = !Each(children)
+ * !countEach(child)
+ * }
+ * } else {
+ * scala.io.Source.fromFile(file).getLines.size
+ * }
+ * }
+ *
+ * val countCurrentSourceFile = countEach(new File(sourcecode.File()))
+ *
+ * inside(countCurrentSourceFile) {
+ * case Stream(lineCount) =>
+ * lineCount should be > 0
+ * }
+ *
+ * }}}
+ *
+ * Unlike Haskell's do-notation or Idris's !-notation,
+ * Dsl.scala allows non-monadic keywords like [[com.thoughtworks.dsl.keywords.Each Each]] works along with
+ * monads.
+ * @example Dsl.scala also supports [[scalaz.MonadTrans]].
+ *
+ * Considering the line counter implemented in previous example may be failed for some files,
+ * due to permission issue or other IO problem,
+ * you can use [[scalaz.OptionT]] monad transformer to mark those failed file as a [[scala.None None]].
+ *
+ * {{{
+ * import scalaz._
+ * import java.io.File
+ * import com.thoughtworks.dsl.keywords.Monadic, Monadic.implicitMonadic
+ * import com.thoughtworks.dsl.domains.scalaz._
+ * import scalaz.std.stream._
+ *
+ * def countLift(file: File): OptionT[Stream, Int] = OptionT.some {
+ * if (file.isDirectory) {
+ * file.listFiles() match {
+ * case null =>
+ * // Unable to open `file`
+ * !OptionT.none[Stream, Int]
+ * case children =>
+ * val child: File = !Stream(children: _*)
+ * !countLift(child)
+ * }
+ * } else {
+ * scala.io.Source.fromFile(file).getLines.size
+ * }
+ * }
+ *
+ * val countCurrentSourceFile = countLift(new File(sourcecode.File())).run
+ *
+ * inside(countCurrentSourceFile) {
+ * case Stream(Some(lineCount)) =>
+ * lineCount should be > 0
+ * }
+ * }}}
+ *
+ * Note that our keywords are adaptive to the domain it belongs to.
+ * Thus, instead of explicit `!Monadic(OptionT.optionTMonadTrans.liftM(Stream(children: _*)))`,
+ * you can simply have `!Stream(children: _*)`.
+ * The implicit lifting feature looks like Idris's effect monads,
+ * though the mechanisms is different from `implicit lift` in Idris.
+ * @see [[Dsl]] for the guideline to create your custom DSL.
+ * @see [[domains.scalaz]] for using [[Dsl.Keyword#unary_$bang !-notation]] with [[scalaz]].
+ * @see [[domains.cats]] for using [[Dsl.Keyword#unary_$bang !-notation]] with [[cats]].
*/
package object dsl
package dsl {
- /** Contains built-in domain-specific [[com.thoughtworks.dsl.Dsl.Keyword Keyword]]s and their corresponding
- * interpreters.
+ /** Contains built-in domain-specific [[com.thoughtworks.dsl.Dsl.Keyword Keyword]]s and their corresponding interpreters.
*/
package object keywords
}
diff --git a/package/src/test/scala/com/thoughtworks/dsl/MockPingPongServer.scala b/package/src/test/scala/com/thoughtworks/dsl/MockPingPongServer.scala
index 3dee6ebc8..22c81a2f1 100644
--- a/package/src/test/scala/com/thoughtworks/dsl/MockPingPongServer.scala
+++ b/package/src/test/scala/com/thoughtworks/dsl/MockPingPongServer.scala
@@ -5,8 +5,7 @@ import org.scalatest.{AsyncTestSuite, BeforeAndAfterAll, Suite}
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.Duration
-/** @author
- * 杨博 (Yang Bo)
+/** @author 杨博 (Yang Bo)
*/
@enableMembersIf(scala.util.Properties.versionNumberString.matches("""^2\.1(1|2)\..*$"""))
trait MockPingPongServer extends BeforeAndAfterAll { this: Suite =>