Introduction of DSL

What is DSL?

  • The so-called Domain specific Language (DSL) is a computer Language that is specific to a specific problem rather than a general purpose Language that covers all software problems. The above is my official answer, is it difficult to understand?
  • DSLS are not exclusively available for Kotlin, but as an Android developer you must be using DSLS and have been using DSLS all the time.
  • In short, DSL languages allow you to build your own syntactic structures, and there isn’t just one way to implement DSLS in Kotlin. The main way to implement DSLS is through higher-order functions, which will be covered in a later column
  • Let’s take a look at the build.gradle code
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation 'androidx. Appcompat: appcompat: 1.2.0' implementation 'androidx. Core: the core - KTX: 1.3.1' implementation 'androidx. Constraintlayout: constraintlayout: 2.0.1' testImplementation junit: junit: '4.12' androidTestImplementation 'androidx. Test. Ext: junit: 1.1.2' androidTestImplementation 'androidx. Test. Espresso: espresso - core: 3.3.0'}Copy the code
  • Gradle is a Groovy-based build tool, and the above code is actually Groovy’s DSL functionality.

Common DSL

Common DSLS can be seen in many areas, such as:

  • Software build domain Ant
  • UI Designer HTML
  • Hardware designer VHDL

The difference between DSL and general-purpose programming languages

  • DSLS for non-programmers, for domain experts;
  • DSLS have a higher level of abstraction and don’t involve details like data structures;
  • DSLS have limited expressiveness and can only describe models in the domain, whereas general-purpose programming languages can describe arbitrary models.

DSL classification

DSLS are divided into:

  • Internal DSL (built from a host language
  • External DSLS (languages built from scratch, need to implement parsers, etc.)

DSL Basics

Next, how does a DSL construct its own syntax in Kotlin

  1. We will create a Dependency (I/O), name it anything, and declare a List array to add data to the list. The class code is as follows:
class Dependency {
    var libs = mutableListOf<String>()
    fun implementation(lib: String) {
        libs.add(lib)
    }
}
Copy the code
  1. Next, we define a higher-order function that takes an extension of Dependency
fun dependencies(block: Dependency.() -> Unit): List<String> {
    val dependency = Dependency()
    dependency.block()
    return dependency.libs
}
Copy the code

This code will be easy to understand if you are familiar with higher-order functions. The parameter in the higher-order function is an extension of Dependency, so we need to initialize a Dependency, call the parameter by instance, and execute the Lambda expression passed in. Let’s create a new test. kt and use it in the main method as follows

dependencies {
        implementation("com.jacky.ll")
        implementation("com.jacky.hh")
    }
Copy the code

Since the defined method returns a List, you can print it out as follows:


var list = dependencies {
        implementation("com.jacky.ll")
        implementation("com.jacky.hh")
    }
    for (text in list) {
       println("$text")
    }
Copy the code
  • Run the program, and the result is shown below
com.jacky.ll
com.jacky.hh
 
Process finished with exit code 0
Copy the code

Learn about Gradle in Android

Introduction of Groovy

  • A scripting language that runs on the JVM and seamlessly integrates with the Java language, see IBM-developerWorks – Mastering Groovy.

Open the Build. gradle file on Android and you’ll see some syntax similar to the following.

Dependencies buildscript {repositories {jcenter ()} {the classpath 'com. Android. View the build: gradle: 1.5.0' / / NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }Copy the code

Executables (repositories) and Dependencies (Repositories) can be configured in BuildScript. Executables (Repositories) and Dependencies can be configured in Their own properties. We can see that through this form of configuration, we can see some of the customizations of the entire project in a clear hierarchy, and since Android also follows the design philosophy of convention over configuration, we can easily personalize the build process by simply changing the parts that need to be customized.

Groovy scripts – build. Gradle

With Groovy, you can write a script file and execute it like a scripting language like Python without having to write a Class and define a main() function like Java. Groovy is a scripting language and Gradle is a building tool based on Groovy. It is also easy to execute and build the entire project from a script. Gradle is a Gradle based project. Settings. Gradle is a Gradle based project. Gradle is a Gradle based project. When we type gradle Clean aDebug on the keyboard, Gradle looks for these files and reads them in sequence using Groovy to parse them.

Groovy syntax

To understand how these DSLS in the build.gradle file are parsed, you need to introduce some of Groovy’s syntactical features as well as some of its advanced features. Here are some of Groovy’s features in a few ways.

Chain command

  • Groovy scripts have Command chains, which translate a(b).c(d) when you write a, B, C, and D in your Groovy scripts. The c method in the returned Instance is then called again with d as a parameter.
  • The parameters B and D are passed as a Closure. Such as:
// equivalent to: turn(left).then(right) turn left then right // equivalent to: take(2.pills).of(chloroquinine).after(6.hours) take 2.pills of chloroquinine after 6.hours // equivalent to: paint(wall).with(red, green).and(yellow) paint wall with red, green and yellow // with named parameters too // equivalent to: check(that: margarita).tastes(good) check that: margarita tastes good // with closures as parameters // equivalent to: Given ({}).when({}).then({}) Given {} when {} then {} Groovy also supports passing empty arguments to a method, but you need to put parentheses around the method with that empty argument. // equivalent to: Select (all).unique(). From (names) select all unique() from names if Command chains have odd parameters, then the last parameter is treated as a Property. Equivalent to: take(3).cookies // and also this: take(3).getcookies () take 3 cookiesCopy the code

Operator overloading

  • With Groovy’s Operator overloading, == is converted to equals, so you can use == freely to compare two strings to see if they are equal. You can also use it when writing gradle scripts.
  • See the official Operator overloading tutorial for all of Groovy’s Operator overloading

entrust

DelegatesTo are an important factor in Gradle’s choice of Groovy as a DSL execution platform. Custom control structures can be easily customized using DelegatesTo, as shown in the following code.

email {
    from '[email protected]'
    to '[email protected]'
    subject 'The pope has resigned!'
    body {
        p 'Really, the pope has resigned!'
    }
}
Copy the code

Next, take a look at the code generated by parsing the DSL language above.

def email(Closure cl) {
    def email = new EmailSpec()
    def code = cl.rehydrate(email, this, this)
    code.resolveStrategy = Closure.DELEGATE_ONLY
    code()
}
Copy the code

EmailSpec is a Class that inherits all of the methods in the cl Closure of arguments, such as from, to, etc. Copy CL to a new Instance using the rehydrate method and assign it to code, code Instance, Associating cl with email by setting the delegate, Owner, and thisObject properties in the Rehydrate method gives a delegate relationship that can be understood as follows: Methods such as from and to in cl closures call methods in the Email delegate Instance and have access to the email Instance variable (Field). DELEGATE_ONLY indicates that a Closure method call is delegated only to its delegate of Closure, and finally starts executing The Closure method using code().

Extension: How else can DSK be used

DSLS can also transform code that conforms to standard API specifications into natural language that humans understand

What does that mean? what

  1. First, let’s create a User object, create user.kt, and rewrite the toString method for printing, as follows:
data class User(var name: String = "", var age: Int = 0) {
    override fun toString(): String {
        return "My name is $name ,i am $age years old"
    }
}
Copy the code
  1. Still testing the code in test.kt, how do we create a User object according to the API specification
val user = User("jacky", 22)
    println(user)
Copy the code

The running result is as follows:


My name is jacky ,i am 22 years old
 
Process finished with exit code 0
Copy the code
  1. To create a User object using a DSL, we first need to provide a higher-order function
fun create(block: User.() -> Unit): User {
    var user = User()
    block(user)
    return user
}
Copy the code

We define a higher-order function of type User extension function that calls the part of the expression through block so we can create a User object directly like this:

val user1 = create {
    name = "jacky"
    age = 22
}
println(user1)
Copy the code

This way is more reasonable, and the results are consistent with the above, so I won’t show you

conclusion

DSL usage scenarios are far more than these, in fact, the premise is to use high order functions, many examples have talked about the use of DSL to generate HTML code, but in the business did not get his role, want to know friends can communicate with me privately. In fact, regardless of any kind of technology, a framework, we can not judge its good or bad, the existence is reasonable, to promote the project is king. Well, that’s the basics of DSL. Welcome to continue learning