Hi, all,
Recently I released Dsl.scala 1.3.0, a framework to create embedded DSLs (Domain-Specific Languages) in Scala.
Dsl.scala can be considered as an alternative syntax to for
comprehension, Scala Async and Scala Continuations. It unifies monads, generators, asynchronous functions, coroutines and continuations to a single universal syntax, and can be easily integrate to Scalaz, Cats, Scala collections, Scala Futures, Akka HTTP, Java NIO, or your custom domains.
A DSL author is able to create language keywords in few lines of code. No knowledge about Scala compiler or AST macros is required. DSLs written in Dsl.scala are collaborative with others DSLs and Scala control flows. A DSL user can create functions that contains interleaved DSLs implemented by different vendors, along with ordinary Scala control flows.
New features
There are 68 commits between Dsl.scala 1.0.0 and 1.3.0. The highlighted new feature is the for
comprehension support. Other features include asynchronous IO for files and exception handling in Future
domains.
Asynchronous file IO
Two keywords, AsynchronousIo.ReadFile
and AsynchronousIo.WriteFile
, are added in Dsl.scala 1.3, for performing Asynchronous file IO on Java NIO file channels. The two new keywords are similar to existing AsynchronousIo.Read
and AsynchronousIo.Write
, except they accept an additional parameter for the position in file.
Asynchronous file IO keywords can be used in functions that return Task
or other continuations. The following example create a function to create an asynchronous loop to read entire file content into memory:
def readAll(channel: AsynchronousFileChannel, bufferSize: Int = 4096): Task[ArrayBuffer[CharBuffer]] = Task {
val charBuffers = ArrayBuffer.empty[CharBuffer]
val decoder = Codec.UTF8.decoder
val byteBuffer = ByteBuffer.allocate(bufferSize)
var position = 0L
while (!AsynchronousIo.ReadFile(channel, byteBuffer, position) != -1) {
position += byteBuffer.position()
byteBuffer.flip()
charBuffers += decoder.decode(byteBuffer)
byteBuffer.clear()
}
charBuffers
}
for
comprehension support
Dsl.scala 1.2+ supports for
comprehension. Now you can create a single for
block to extract and compose values from different types of keywords. 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 Each
keyword, managed native resources with the help of Using
, performs previously created readAll
task with the help of a Shift
keyword, and finally converts the return type as
an asynchronous task to produce a vector.
def cat(paths: Path*) = {
for {
path <- Each(paths)
channel <- Using(AsynchronousFileChannel.open(path))
charBuffers <- Shift(readAll(channel))
charBuffer <- Each(charBuffers)
char <- Each(charBuffer.toString)
} yield char
}.as[Task[Vector[Char]]]
The above for
-comprehension expression has the same functionality as the following code in !-notation.
def cat(paths: Path*): Task[Vector[Char]] = {
val path = !Each(paths)
val channel = !Using(AsynchronousFileChannel.open(path))
val charBuffers = !Shift(readAll(channel))
val charBuffer = !Each(charBuffers)
val char = !Each(charBuffer.toString)
!Return(char)
}
If you only use for
-comprehension and never use !-notation, you don’t need compilerplugins-bangnotation
nor compilerplugins-reseteverywhere
mentioned in Getting Started.
Out-of-the-box exception handling for Future
s
In Dsl.scala 1.0.x, the ability of exception handling for Scala Future
is only achieved by MonadError
type classes in Cats or Scalaz. In order to use try
/ catch
/ finally
expressions with !-notation in Dsl.scala 1.0.x, you need the dependencies of Scalaz or Cats.
// Need the following imports in Dsl.scala 1.0:
//
// import com.thoughtworks.dsl.domains.scalaz._
// import scalaz.std.scalaFuture._
//
// Or:
//
// import com.thoughtworks.dsl.domains.cats._
// import cats.instances.future._
//
def failedFuture = Future { 0 / 0 }
def recovered = Future {
try {
100 + !Await(failedFuture)
} catch {
case _: ArithmeticException =>
42
}
}
In Dsl.scala 1.1+, exception handling for Future
s is now built-in. You can use try
/ catch
/ finally
out of the box. No dependency to Scalaz or Cats is required. This makes Dsl.scala a clean replacement to scala.async
.
Note that you still need MonadError
instances for the exception handling feature for Monix or Cats Effect.
Other utilities
I also published Dsl.scala-akka-http recently, the Akka HTTP integration for Dsl.scala.
Backward and forward compatibility
All Dsl.scala versions conform to semantic versioning for binary compatibility. Dsl.scala 1.3.0 is binary backward compatible with all Dsl.scala 1.x versions, and all 1.3.x versions will be forward compatible with other 1.3.x version.
Source level backward compatible is broken in Dsl.scala 1.3. The keyword Each
used in Unit
domain does not compile any more. Use Foreach
instead if you want to iterate a collection without mapping it to other values.
Author information
Dsl.scala is founded and maintained by me when I was in ThoughtWorks, and I will keep maintaining this library in my part time, though I have resigned from ThoughtWorks recently. Since I am back to the job market, you can contact me or give me a job referral if you recognize my expertise in my open source contributions. I am currently seeking for career opportunity in California, especially in the San Francisco bay area or San Diego.