DSL and the definition of DSL itself is a vague concept, so it is inevitable that there are different opinions from others. If there are different opinions, we can discuss them in detail.

Recently, I did a share about the application of DSL in iOS development in the company. This article will briefly introduce the content of this share.

Although the title of this article is to talk about DSL and DSL application, but the main focus of this article is still DSL, will briefly introduce the APPLICATION of DSL in iOS development (CocoaPods).

No silver bullet?

In 1987, Fred Brooks, the father of IBM’s mainframe computer, published a paper about Software Engineering, No Silver Bullet — Essence and Accidents of Software Engineering, which mainly focused on the following points: There is no technology or approach that will increase software engineering productivity tenfold in a decade.

There is no single development, in either technology or management technique, which by itself promises even one order-of-magnitude improvement within a decade in productivity, in reliability, in simplicity.

Today, we don’t talk about whether silver bullets exist in software engineering (this phrase is very useful when the boss or project manager wants to speed up the project). As a developer, we don’t care much about this abstract theory. We are more concerned about whether the development efficiency can be substantially improved.





silver-bullet

Today’s DSL can actually increase productivity, reduce unnecessary work, and help us meet requirements faster in some areas.

What is DSL?

Two years ago, I heard the word DSL in a sharing session of my freshman year, but I did not have a deep understanding and understanding of the term at that time, so I forgot it. However, some open source projects I have done recently remind me of DSL again, which is also the origin of this sharing topic.

DSL is actually the abbreviation of Domain Specific Language, which is translated into Domain Specific Language (HEREINAFTER referred to as DSL) in Chinese. The opposite of DSL is the GPL, which is not an open source license as we know it, but General Purpose Language, General Programming Language, The familiar objective-C, Java, Python, C, and so on.

Wikipedia’s definition of a DSL is relatively simple:

A specialized computer language designed for a specific task.

A computer language designed specifically to solve a particular type of task.

In contrast to the GPL, DSLS are very different from the traditional general-purpose programming languages C, Python, and Haskell. A general-purpose computer programming language can be used to write any computer program, can express any computable logic, and is Turing-complete.

The DSL in this section refers to the external DSL, and the internal DSL/ embedded DSL is covered in the next section

DSLS, however, are not Turing-complete. They have limited expressive power, but are designed to solve specific tasks in specific domains.

A computer programming language of limited expressiveness focused on a particular domain.

Martin Fowler, another world-renowned software developer, has defined DSLS more specifically in my view, as being efficient in a particular domain by compromising expressiveness.

Limited expressiveness becomes the boundary between the GPL and DSL.

A few chestnuts

Some of the most common DSLS include Regex and HTML & CSS, and a few examples will be covered briefly here

  • Regex
    • The regular expression only specifies the pattern of the string. The engine determines whether the current string matches the regular expression based on the pattern.



      regex
  • SQL
    • SQL statements are not really executed when they are used. The SQL statements we enter are finally handed to the database for processing. The database reads useful information from the SQL statements, and then returns the desired results from the database.
  • HTML & CSS
    • HTML and CSS simply describe the structural semantics and styles of Web interfaces, and while they are important in building websites, they are not programming languages. Instead, we can think of HTML and CSS as domain-specific languages in the Web.

Features

The above 🌰 obviously narrow down the concept of a general-purpose programming language, but they do work very well in their own domain, because these DSLS are shaped by the characteristics of a particular domain; General-purpose programming languages, on the other hand, are designed to solve more abstract problems than domain-specific languages, and focus on more than one domain.

The examples above share some common characteristics:

  • There is no concept of calculation and execution;
  • It doesn’t need to directly represent computation in itself;
  • You only need to declare rules, facts, and hierarchies and relationships between certain elements;

Although we know about DSLS and some of their features, so far, we still don’t have a clear idea of how to build one.

To build a DSL

Building a DSL is similar to a programming language. Think about what we need to do when we reimplement a programming language; The process of implementing a programming language can be reduced to defining syntax and semantics, and then implementing a compiler or interpreter. DSL implementation is very similar, and we need to design a DSL syntactically and semantically.





compile

To sum up, there are two things that need to be done to implement a DSL:

  1. Design the syntax and semantics, and define what elements in the DSL look like and what they stand for
  2. Implements parser, parses the DSL, and finally executes it through the interpreter

Take HTML as an example, all elements in HTML are contained in Angle brackets <>, and different elements in Angle brackets represent different tags. These tags will be parsed into DOM trees by browsers, and then drawn by invoking Native graphics API through a series of processes.





dom-tree

For example, we define a model to implement a DSL for the ORM domain in the following way:

define :article do
  attr :name
  attr :content
  attr :upvotes, :int

  has_many :comments
endCopy the code

In the above DSL, use define to define a new model, use attR to add attributes to the model, and use has_many to establish one-to-many relationships in the data model. This “string” can be parsed using a DSL and handed over to a code generator to generate code.

public struct Article { public var title: String public var content: String public var createdAt: Date public init(title: String, content: String, createdAt: Date) static public func new(title: String, content: String, createdAt: Date) -> Article static public func create(title: String, content: String, createdAt: Date) -> Article? . }Copy the code

The number of elements in the DSL created here is very small, with just a few keywords like define attr and has_many, but these keywords do most of the semantics that need to be expressed at the model layer.

Design principles and compromises

The biggest design principle of DSLS is simplicity, which reduces the user’s burden by simplifying elements of the language. Whether it’s Regex, SQL, HTML, or CSS, the documentation is usually just a few pages long and easy to learn and master. The problem, however, is the lack of abstract concepts in DSLS, such as modularity, variables, and methods.

Abstract concepts are not a domain concern, just as Regex does not need modules, variables, and methods.

Because of the lack of abstraction capabilities, DSLS often fell short of developers’ needs as our projects grew larger and larger; We still need to complement DSLS with concepts like modularity in programming languages to solve the problem that DSLS are not really programming languages.





css-sass

In today’s Web front-end projects, we tend to use Sass or Less to bring more abstractions to CSS, such as nesting rules, variables, blending, and inheritance, rather than writing CSS files by hand when developing large-scale projects.

nav { ul { margin: 0; padding: 0; list-style: none; } li { display: inline-block; } a { display: block; padding: 6px 12px; text-decoration: none; }}Copy the code

In other words, as projects using DSLS grow in size, developers extend existing DSLS by adding abstraction capabilities. However, such extensions often require reimplementing features in a general-purpose programming language, so they are generally complex.

Embedded DSL

So, is there another way to quickly add abstraction capabilities to DSLS? And that’s the topic of this section, embedded DSL.

The DSL described in the previous section could actually be called an external DSL; The embedded DSL I’m going to talk about here also has an alias, internal DSL.

The biggest difference between the two is that the internal DSL implementation is often embedded in some programming language, such as CocoaPods, a dependency management component for iOS, which is based on Ruby features, and Gradle, a compilation tool for Android, which is based on Groovy.





cocoapods

CocoaPods and other embedded DSLS use the host language’s abstraction capabilities and eliminate the need to implement complex parsers, modules, variables, and other features.

The creation of embedded DSLS blurs the boundary between frameworks and DSLS, but there is no obvious difference between the two. However, DSLS are typically created using the features of the host language and are designed without regard to the apis and methods available in the host language, whereas frameworks typically combine and repackage the apis in the language.

We don’t have to argue about what is a framework and what is a DSL, because those arguments don’t make sense.

The Rails and Embedded DSL

The best known and most successful embedded DSL is Ruby on Rails, and while Rails is arguably a DSL, Rails provides a lot of built-in support for Web application creation, making it very easy to develop Web applications.





rails

Ruby, DSL and iOS

For the sake of completeness, some of the content in this section is from the previous article. What does CocoaPods do? .

As an iOS and Rails developer, I work with a lot of DSLS, and the most common DSL in iOS development is CocoaPods. Here’s how to create an embedded DSL using Ruby, using CocoaPods as an example.

According to the Ruby?

There are four reasons why you might ask why you create embedded DSLS in Ruby instead of C, Java, Python, etc. :

  • The nature of everything objects reduces the number of elements in the language. There are no primitive types, no operators.
  • Passing code blocks into Ruby methods is convenient;
  • As a language for explaining execution, Eval blurs the boundary between data and code;
  • There are no constraints on the format of the code, and conventions reduce noise in the code.

Everything is an object

In many languages, such as Java, numbers and other primitive types are not objects. In Ruby, all elements, including primitive types, are objects and there is no concept of operators. The so-called 1 + 1 is just the syntactic sugar of 1.

Thanks to the concept of everything as an object, in Ruby you can send methods to any object and introspect at runtime, so EVERY time I forget a method, I use methods directly to “look up the document” :

2.3.1:003 > 1. The methods = > [: %, &, : * : +, : - : /, : <, >, : ^, : |, :, : - the @ : * * : < = > : < <, : > >,, < =, : > =,, = =, = = =, :[], :inspect, :size, :succ, :to_s, :to_f, :div, :divmod, :fdiv, :modulo, ...]Copy the code

Calling methods to object 1 here, for example, returns all methods it can respond to.

Not only does everything object reduce the number of types in the language and eliminate the boundary between basic data types and objects; This concept also simplifies the elements that make up the language, so there are only objects and methods in Ruby, which greatly reduces the complexity of the language:

  • Use objects to store state
  • Objects communicate with each other through methods

block

Ruby’s support for the functional programming paradigm is through blocks, which are somewhat different from blocks in Objective-C.

First, a block in Ruby is an object, an instance of a Proc class, that is, all blocks are first-class and can be passed as arguments and returned.

The following code demonstrates two ways to pass a code block to a Ruby method:

def twice(&proc)
    2.times { proc.call() } if proc
end

def twice
    2.times { yield } if block_given?
endCopy the code

Yield will call an external block, block_given? Used to determine whether the current method passed a block.

twice do 
    puts "Hello"
end

twice { puts "hello" }Copy the code

Passing blocks to the TWICE method is also very simple, and you can pass code blocks to any Ruby method using do, end, or {,}.

eval

Lisp has had eval for decades. The eval method executes a string as code, meaning that eval blurs the boundary between code and data.

> eval "1 + 2 * 3"
 => 7Copy the code

With the eval method, we gain a much more dynamic ability to use strings to change control flow, execute code, and make direct use of the current language interpreter at run time. Instead of having to manually parse the string and execute the code.

Formats and Conventions

Writing Ruby scripts does not require strict formatting rules as Python does. There are no blank lines or tabs, and you can write whatever you want, which greatly increases the possibilities for DSL design.

Also, in general, Ruby doesn’t need parentheses on method calls:

puts "Wello World!" puts("Hello World!" )Copy the code

This reduces the noise in the DSL, helps us care more about syntax and semantic design, and reduces the likelihood of user errors.

Finally, there is a special data format in Ruby called Symbol:

> :symbol.to_s
 => "symbol"
> "symbol".to_sym
 => :symbolCopy the code

Symbol can be seamlessly converted to and from strings using methods built into Ruby. As a substitute for string, it can also reduce the cost of error and improve the user experience. We do not need to write a string surrounded by quotation marks, just need to create a Symbol object beginning with:.

What is Podfile

Now that we know a little about Ruby, we can take a look at what podfiles are in projects using CocoaPods:

Source 'https://github.com/CocoaPods/Specs.git' target 'Demo' do pod 'Mantle', '~ > 1.5.1'... endCopy the code

If you’re not familiar with iOS developers who don’t use CocoaPods, here’s a quick look at some of the information in this file.

Source can be regarded as the source address that stores the dependency meta-information (including the corresponding GitHub address of the dependency).

Target specifies the name of the project to which the dependency needs to be added.

Pod stands for dependency, Mantle for dependent framework, followed by version number.

Here is an example of using Podfile to define a dependency, but the Podfile description of a constraint actually looks like this:

Source (' https://github.com/CocoaPods/Specs.git ') target (' Demo ') do pod (' Mantle ', '~ > 1.5.1')... endCopy the code

The constraints described in the Podfile are shorthand for code that will be parsed as Ruby code.

Create a simple Embedded DSL

Implementing an embedded DSL using Ruby generally requires three steps. Here’s CocoaPods as an example:

  • Create a context for the execution of the “code” in the Podfile, that is, the methods;
  • Read the contents of the Podfile into the script;
  • useevalExecutes the “code” in the Podfile in context;

The principle of

CocoaPods for DSL implementation is basically the process of creating a DSL, defining a set of necessary methods, such as source, POD, etc., to create an execution context; It then reads the file storing the DSL and executes using eval.

Information is generally transmitted through parameters, such as:

source 'https://github.com/CocoaPods/Specs.git'Copy the code

The parameters to the source method are the Git addresses that depend on the meta Specs, which are read into CocoaPods during eval and analyzed.

implementation

Here is a very common Podfile:

source 'http://source.git' platform :ios, '8 'target 'Demo' will you post it next to the navigation' post ' 'IQKeyboardManager' pod 'IQDropDownTextField' endCopy the code

Because source, platform, target, and pod are all methods, here we need to build a context that contains the above methods:

# eval_pod.rb
$hash_value = {}

def source(url)
end

def target(target)
end

def platform(platform, version)
end

def pod(pod)
endCopy the code

A global variable hash_value is used to store the dependencies specified in the Podfile, and a skeleton Podfile parsing script is built; Instead of perfecting the implementation details of these methods, let’s try reading the contents of the Podfile and executing eval to see if there’s a problem.

Add these lines at the bottom of the eval_pod.rb file:

content = File.read './Podfile'
eval content
p $hash_valueCopy the code

This reads the contents of the Podfile, executes the contents as a string, and finally prints the value of hash_value.

$ ruby eval_pod.rbCopy the code

Running this Ruby code produces no output, but it does not report any errors, so we can refine these methods:

def source(url)
    $hash_value['source'] = url
end

def target(target)
    targets = $hash_value['targets']
    targets = [] if targets == nil
    targets << target
    $hash_value['targets'] = targets
    yield if block_given?
end

def platform(platform, version)
end

def pod(pod)
    pods = $hash_value['pods']
    pods = [] if pods == nil
    pods << pod
    $hash_value['pods'] = pods
endCopy the code

After adding the implementation of these methods, running the script again will get the dependency information in the Podfile, but the implementation here is very simple and many cases are not handled:

$ ruby eval_pod.rb
{"source"=>"http://source.git", "targets"=>["Demo"], "pods"=>["AFNetworking", "SDWebImage", "Masonry", "Typeset", "BlocksKit", "Mantle", "IQKeyboardManager", "IQDropDownTextField"]}Copy the code

But that’s about the process of building an embedded DSL in Ruby, using features built into the language to create a DSL that doesn’t look like code when used.

Write in the back

Finally, I would like to say that when we often need to solve repetitive problems in a certain area, we can consider implementing a DSL specifically to solve these similar problems.

Using an embedded DSL is a great way to address these issues without having to reimplement the interpreter and take advantage of the host language’s abstraction capabilities.

At the same time, as embedded DSLS expand the scope of DSLS, it doesn’t matter whether something is a framework or a DOMAIN specific language. What matters is whether we can jump out and use the methods described in this article to reduce our workload when we encounter some problems.

Reference

  • No Silver Bullet — Essence and Accidents of Software Engineering
  • Domain-specific language
  • DomainSpecificLanguage
  • How browsers work

other

Making Repo: iOS – Source Code – Analyze

Follow: Draveness dead simple

Source: draveness.me/dsl