Use Domain-Specific Extension Functions

Suppose we’re designing a DSL in which a user may want to specify dates relative to the current date. Conversationally, we’re likely to say something like "two days ago" rather than Date.now().daysBefore(2). In other words, we would want to make something like this possible:

 2 days ago
 5 days after

Looking at the two lines syntactically, we can quickly translate them:

 2.days(ago)
 5.days(after)

But that raises two questions: what are ago and after, and does an Int have a function called days that is also marked as infix? The short answer to those two questions are “no clue” and “unfortunately no,” respectively. But that shouldn’t dissuade the designers of internal DSLs.

Let’s work on making that DSL snippet run as-is on top of Kotlin.

First we can define the words ago and after as variables that are available in the execution context of the DSL. That leaves the function days() to be addressed. The number 5, which is of type Int in the Kotlin Standard Library, doesn’t have a days() function. However, Kotlin provides an easy way to add extension functions to preexisting classes. We can use that facility to add a custom days() function to the Int class.

Let’s create a file named IntExt.kt which will hold the code necessary to process the DSL snippet in the file days.kts. We’ll first define two constants, ago and after, which refer to instances of the custom enum class, TimeAdverbs. Then we’ll implement the days function as an extension function on the Int class.

 enum​ ​class​ TimeAdverbs { ago, after }
 
 infix​ ​fun​ Int.​days​(duration: TimeAdverbs) = ​when​(duration) {
  TimeAdverbs.ago -> println(​"That's $this days ago"​)
  TimeAdverbs.after -> println(​"That's $this days from now"​)
 }
 
 val​ ago = TimeAdverbs.ago
 val​ after = TimeAdverbs.after

The days() function takes a parameter of type TimeAdverbs and uses the argument matching facility in Kotlin, the when clause, to take different actions depending on the value of the parameter.

By using the extension function capability, we added the function days() to the Int class. Thanks to this small effort, we’re able to make the code flow naturally, with a conversational tone.

It’s pretty straightforward to use the code in IntExt.kt to process the DSL snippet in days.kts. First, we must compile the code in IntExt.kt into bytecode. Then we can execute the code in days.kts as a script, providing the bytecode location in the classpath:

 kotlinc-jvm -d classes IntExt.kt
 
 kotlinc-jvm -classpath classes -script days.kts

We first compile the IntExt.kt file using the kotlinc-jvm command with the -d option. This places the generated bytecode into the classes directory. Then, using the same command, but with the -script option, we execute the code in the days.kts file. When the script expects the definition of ago and after and the function days, the kotlinc-jvm tool is able to fetch that from the bytecode located in the specified classpath.

The output of the execution of the commands is shown here:

 That's 2 days ago
 That's 5 days from now

This example showed us how we can easily add domain-specific functions to different data types. By using extension functions, we can enhance existing classes or data types. Thus the users of our DSLs can use syntax that’s natural and conversational in the domain targeted by the DSL.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.129.19