Await is a Keyword to extract value from a scala.concurrent.Future.
Await is a Keyword to extract value from a scala.concurrent.Future.
This keyword is available in functions whose return types are
Future, domains.task.Task,
or any exception aware continuations as (_ !! Throwable !! _)
.
杨博 (Yang Bo)
Given a Future:
import scala.concurrent.Future val myFuture40 = Future { 40 }
It can be Await in another Future
import scala.concurrent.Future def myFuture42 = Future { !Await(myFuture40) + 2 }
It can be converted to a domains.task.Task with the help of Await.
import com.thoughtworks.dsl.domains.task._ import com.thoughtworks.dsl.keywords._ val myTask = Task { !Await(myFuture42) }
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)
杨博 (Yang Bo)
The base type of Continue keyword.
杨博 (Yang Bo)
杨博 (Yang Bo)
A keyword for extracting monadic value from the monadic expression fa.
A keyword for extracting monadic value from the monadic expression fa.
Monadic should be a scala.AnyVal after https://github.com/scala/bug/issues/10595 is resolved.
com.thoughtworks.dsl.domains.scalaz for using this Monadic keyword with scalaz.Monad.
com.thoughtworks.dsl.domains.cats for using this Monadic keyword with cats.Monad.
NullSafe is a keyword to perform null
check.
NullSafe is a keyword to perform null
check.
杨博 (Yang Bo)
The ? operator usually works with Java libraries that may produce null
.
import com.thoughtworks.dsl.keywords.NullSafe._ val myMap = new java.util.HashMap[String, String](); ((myMap.get("key1").? + myMap.get("key2").?): @ ?) should be(null)
You can use ? annotation to represent a nullable value.
import com.thoughtworks.dsl.keywords.NullSafe._ case class Tree(left: Tree @ ? = null, right: Tree @ ? = null, value: String @ ? = null) val root: Tree @ ? = 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[NullPointerException] should be thrownBy { root.right.left.right.value }
The above code throws an exception because root.right.left
is null
.
The exception can be avoided by using ? on a nullable value:
root.?.right.?.left.?.right.?.value should be(null)
The entire expression will be null
if one of ? is performed on a null
value.
@ ?
.("Hello " + ("world " + root.?.right.?.left.?.value)) should be("Hello world null") ("Hello " + (("world " + root.?.right.?.left.?.value.?): @ ?)) should be("Hello null") (("Hello " + ("world " + root.?.right.?.left.?.value.?)): @ ?) should be(null)
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.
import com.thoughtworks.dsl.keywords.NullSafe._ val explicitNullable: String @ ? = null ((explicitNullable.? + " Doe") : @ ?) should be(null)
val implicitNullable: String = null ((implicitNullable.? + " Doe") : @ ?) should be(null)
A type is considered as not nullable if it is a value type.
val implicitNotNullable: Int = 0 "(implicitNotNullable.? + 42) : @ ?" shouldNot compile
Alternatively, a type can be considered as not nullable by explicitly converting it to NotNull.
val explicitNotNullable: NotNull[String] = NotNull("John") """(explicitNotNullable.? + " Doe") : @ ?""" shouldNot compile
NoneSafe for similar checks on scala.Options.
Put is a Keyword to put the value to the context.
Put is a Keyword to put the value to the 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.
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.
杨博 (Yang Bo)
Put and Get support multiple states.
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]) !Return((!Get[Vector[Any]]).mkString) } formatter(0.5)(42)(Vector.empty) should be("x=0.5,y=42")
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) val upperCased = !Get[String]() Function.const(upperCased.last) }
For example, given a string of foo
, the upper-cased last character should be O
.
// Output: O upperCasedLastCharacter("foo") should be('O')
A Dsl.Keyword to early return a lifted value from the enclosing function.
A Dsl.Keyword to early return a lifted value from the enclosing function.
杨博 (Yang Bo)
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"); } val r = randomInt().result r should be < 100 r % 10 should not be r / 10
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"); } val r = randomInt().result r should be < 100 r % 10 should not be r / 10
杨博 (Yang Bo)
杨博 (Yang Bo)
杨博 (Yang Bo)
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
A keyword to skip the current iteration in a collection comprehension block.
A keyword to skip the current iteration in a collection comprehension block.
杨博 (Yang Bo)
Each and 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) } 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 } primeNumbersBelow(13) should be(Array(2, 3, 5, 7, 11))
This Continue keyword is usually used with Each, to skip an element in the loop.
Each for creating collection comprehensions.
Contains built-in domain-specific Keywords and their corresponding interpreters.