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 =>