CSC/ECE 517 Fall 2013/ch1 1w30 nn: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
 
(32 intermediate revisions by the same user not shown)
Line 1: Line 1:
A '''domain specific language (DSL)''' is a computer programming language of limited expressiveness focused on a particular domain. A domain-specific language is created specifically to solve problems in a particular domain and is not intended to be able to solve problems outside it (although that may be technically possible).  
A '''domain specific language''' ([http://en.wikipedia.org/wiki/Domain-specific_language DSL]) is a computer programming language of limited expressiveness focused on a particular domain. A domain-specific language is created specifically to solve problems in a particular domain and is not intended to be able to solve problems outside it (although that may be technically possible).  


== Why use DSL ?==
== Why use DSL ?==
Line 26: Line 26:


An external DSL gives the programmer the liberty  to design the syntax of his/her language in exactly the way desired. They can select the language's symbols, operators, constructs, and structure as they fit their domain. On the downside, the programmer has to define the grammar for their language and  create a compiler to parse and process the syntax and map it to the semantics they expect. An external DSL gives a programmer a lot of flexibility, but you have to take the time to do the hard work of compiling it.
An external DSL gives the programmer the liberty  to design the syntax of his/her language in exactly the way desired. They can select the language's symbols, operators, constructs, and structure as they fit their domain. On the downside, the programmer has to define the grammar for their language and  create a compiler to parse and process the syntax and map it to the semantics they expect. An external DSL gives a programmer a lot of flexibility, but you have to take the time to do the hard work of compiling it.


== Building a DSL in Ruby ==
== Building a DSL in Ruby ==
DSls are quite popular in Ruby because of the nature of the Ruby language itself:
DSls are quite popular in Ruby because of the nature of the Ruby language itself:
* Especially the ability to use minimal syntax in order to make the code concise.
* Especially the ability of Ruby to use minimal syntax in order to make the code concise.
* Ruby itself is a very expressive language and has readable syntax which facilitates writing of DSLs where one has to come up with their own syntax.
* Ruby being a very expressive language and having readable syntax facilitates writing of DSLs where one has to come up with their own syntax.
* Most ruby programs are already a DSL because of Ruby's syntax.




Line 37: Line 37:


<pre>
<pre>
it "must not allow a person to login without the proper credentials "
it "must not allow a person to login without the proper credentials " do
  person.logs_in_with: invalid credentials
  person.logs_in_with("invalid credentials");
  person.should_not_be_logged_in
  person.must_not(be(person.logged_in?()));
end
end
</pre>
</pre>


Line 46: Line 47:


<pre>
<pre>
it "must not allow a person to login without the proper credentials "
it "must not allow a person to login without the proper credentials " do
  person.logs_in_with("invalid credentials");
  person.logs_in_with :invalid credentials
  person.must_not(be(person.logged_in?()));
  person.should_not_be_logged_in
end
end
</pre>
</pre>


From the above example we can clearly see that the DSL code is more readable and clearly explains what the code means.
From the above example we can clearly see that the DSL code is more readable and clearly explains what the code means.
 
Notice how we used '''symbols(:invalid credentials)''' instead of strings to make it more readable.


=== How to build a DSL in ruby ===
=== How to build a DSL in ruby ===


A general process for writing a DSL in Ruby is as follows :


=== Examples ===
[[File:DSlruby.png]]
==== Internal DSL in Ruby ====
Following is an example of an internal DSL in Ruby :
Imagine you want to write a quiz program, the kind of thing that kids learning Latin or programmers studying for the Ruby Expert Programmer Certification exam might use to drill themselves on questions and answers. We are going to call our little language Quiz'em. A program written in the Quiz'em language prompts the student with multiple choice questions and keeps track of how many the student gets right. Run a Quiz'em program and it just starts asking questions:


Who was the first president of the USA?
* The '''process identification''' page involves identifying all the processes and functionality to be  built into the DSL.
1 - Fred Flintstone
* The second step involves '''choosing a desired syntax''' for writing the DSL.
2 - Martha Washington
* In the third step we '''define the interface''' of the DSL or the API.
3 - George Washington
* Next we '''define the classes''' and various abstraction put in place.
4 - George Jetson
* Finally we implement all the validations.
Enter your answer:
The idea of Quiz'em is that we have a special little language that expresses the questions and their answers and an interpreter program which will read the questions and answers and administer the quiz. Now the exact syntax of the question and answer language is not really important, as long as it is not too burdensome. Something like this might do nicely:


question 'Who was the first president of the USA?'
=== Good practice for building a Ruby DSL ===
wrong 'Fred Flintstone'
wrong 'Martha Washington'
right 'George Washington'
wrong 'George Jetson'


question 'Who is buried in Grant\'s tomb?'
Follow these good design principles to make a better DSL:
right 'U. S. Grant'
* Always make entities as classes , so that they may correspond to a noun or a realworld entity.
wrong 'Cary Grant'
wrong 'Hugh Grant'
wrong 'W. T. Grant'
Let's assume that we have the two questions above in a file called, questions.qm and see how we can write that Quiz'em interpreter.


Now we might pull out our regular expressions or parser generator and write a traditional parser for the questions above -- first read a word which should be 'question', then look for a quote, then... But there is an easier way. Looking over the questions and answers above, we realize that they could almost be Ruby method calls. Wait! They could be Ruby method calls: if question, right and wrong are all the names of a Ruby methods, then what we have above is perfectly valid Ruby program -- a series of calls to question, right and wrong, each with a single string parameter. If you are not that familiar with Ruby, I should probably mention that in Ruby both the semicolons at the end of statements and the parenthesis around the argument lists are optional.
* All operations should be methods, typically use verbs to define an action.


Just to get things started, let's see if we can write a little Ruby program that does nothing other than read in the question file. Here is the start of our domain specific language interpreter, a little program called quizm.rb:
* Use in class definitions as much as possible.
<pre>
#!/usr/bin/env ruby


def question(text)
== Examples ==
  puts "Just read a question: #{text}"
Here are some examples that will explain the various benefits/use cases of DSLs in Ruby:
end


def right(text)
==== Internal DSL in Ruby ====
  puts "Just read a correct answer: #{text}"
end


def wrong(text)
* A programmer can create his/her own classes and define them:
   puts "Just read an incorrect answer: #{text}"
<pre>
class Integer
def minutes
   .....
end
end
end
</pre>


<pre>load 'questions.qm'</pre>
60.minutes
20.mnutes.later
delete 20.minutes.later


It doesn't look like much, but the code above captures a lot of the ideas that you need to implement an internal DSL in Ruby. First we have the three methods question, right and wrong. Each of these methods take a single, presumably string argument. But the key bit of code for our DSL is the last statement:
</pre>


load 'questions.qm'
We can see in the above code that the programmer has defined his own class Integer within it a method minutes. On the method he has applied various calls that would result in some action (in this case delete).


This statement says to load and execute the contents of the file questions.qm as Ruby program text. This means that all those questions, rights and wrongs, the things that looked like Ruby method calls, are actually going to get sucked into our program and interpreted as Ruby method calls. It is this idea of sucking in the DSL and interpreting it as Ruby that puts the internal into an internal DSL. With that load statement, the interpreter and the Quiz'em program merge. It is very B-movie science fiction. Running quizm.rb, we get the output of all those method calls:
* A DSL can be used to write more expressive code


Just read a question: Who was the first president of the USA?
Just read an incorrect answer: Fred Flintstone
Just read an incorrect answer: Martha Washington
Just read a correct answer: George Washington
Just read an incorrect answer: George Jetson
Just read a question: What is the capital of the UK?
Just read a correct answer: London
Just read an incorrect answer: Paris
Just read an incorrect answer: Washington DC
Just read an incorrect answer: Bedrock
Building a Quiz
Now that we have our question writer unknowingly writing Ruby method calls, what should we really do inside of the methods? What should question and right and wrong actually do? The answer is that they should remember that they were called -- in other words, they should just set up some data structures. First we need some data structures to set up, so let's create some classes. We will probably need an object that represents the whole quiz. Let's call it... Quiz?
<pre>
<pre>
require 'singleton'
every 3.day, :at => '2.00 pm' do
 
delete
class Quiz
  include Singleton
 
  def initialize
    @questions = []
  end
 
  def add_question(question)
    @questions << question
  end
 
  def last_question
    @questions.last
  end
 
  def run_quiz
    count=0
    @questions.each { |q| count += 1 if q.ask }
    puts "You got #{count} answers correct out of #{@questions.size}."
  end
end
end
</pre>
</pre>
The Quiz class is just a collection of questions. Since we are only doing one quiz at a time I made the Quiz class a singleton. The only slightly complex thing about Quiz is the run_quiz method, which actually asks all the questions and reports the number that the student got right.


Next we will need a class for the questions:
The above code easily reflects its purpose which is at a particular frequency and at a specific time carry out some action.
 
* Following is an [http://en.wikipedia.org/wiki/RSpec RSpec] code example :
<pre>
<pre>
class Question
describe Group do
 
it "name XYX " do
  def initialize( text )
  x = Group.new
    @text = text
  x.should_not_be_valid
    @answers = []
end
  end
 
  def add_answer(answer)
    @answers << answer
  end
 
  def ask
    puts ""
    puts "Question: #{@text}"
    @answers.size.times do |i|
      puts "#{i+1} - #{@answers[i].text}"
    end
    print "Enter answer: "
    answer = gets.to_i - 1
    return @answers[answer].correct
  end
end
end
</pre>
</pre>
A question is just some text ("What is 2 + 2?") along with a collection of answers. Like Quiz, Question is a very simple class; the mechanics of actually asking the question taking up more than half the class.


Finally, we need some answers (who doesn't?). Answers are just a string and a boolean telling if this is a right answer or one of those ugly, wrong ones:
* A rails migration is also a DSL
<pre>
<pre>
class Answer
create_table:employees do |x|
   attr_reader :text, :correct
   x.string :name
  def initialize( text, correct )
   x.timestamps
    @text = text
    @correct = correct
   end
end
end
</pre>
</pre>
Pulling our DSL together
 
Notice that in all the above examples the focus is on using your own syntax in making the code concise and easier to use. Herein lies the power of DSL.
Now that we have built all of our supporting code, making the Quiz'm DSL actually work is easy. Let's rewrite our original question, right and wrong methods to use the classes we just wrote:
 
<pre>#!/usr/bin/env ruby
#
require 'quiz'
def question(text)
  Quiz.instance.add_question Question.new(text)
end
 
def right(text)
  Quiz.instance.last_question.add_answer Answer.new(text,true)
end
 
def wrong(text)
  Quiz.instance.last_question.add_answer Answer.new(text,false)
end
 
</pre>
 
<pre>load 'questions.qm'</pre>
 
Quiz.instance.run_quiz
The question method just gets hold of the Quiz singleton instance and adds a new question object to it. Now at the time we actually do this, we haven't collected any of the answers for the question. But not to worry: that is done in the right and wrong methods. These nearly identical methods both go to the Quiz singleton and ask for the last question added to the quiz: Quiz.instance.last_question. When they get that last question, they simply append a new answer to it: right adds a new correct answer, while wrong adds a new incorrect answer.
 
Finally we have the last two lines of our Quiz'em interpreter:
 
load 'questions.qm'
 
Quiz.instance.run_quiz
The load statement we have seen before: it just pulls in our question file as Ruby code. The very last line of the program actually runs the quiz.
 
If you run quizm.rb, your output will look something like this (assuming you have my encyclopedic knowledge of American history):
 
 
Question: Who was the first president of the USA?
1 - Fred Flintstone
2 - Martha Washington
3 - George Washington
4 - George Jetson
Enter answer: 3
 
 
Question: Who is buried in Grant's tomb?
1 - Cary Grant
2 - Hugh Grant
3 - US Grant
4 - Alan Grant
Enter answer: 3
 
 
You got 2 answers correct out of 2.
 
The structure of the Quiz'em interpreter is actually pretty typical of this style of internal DSL. Start off by defining your data structures: in our case the Quiz class and its friends. Next, set up some top level methods which will support the actual DSL language -- in Quiz'em this was the question, right and wrong methods. Next suck in the DSL text with a load statement. Typically the effect of pulling in the DSL text is to fill in your data structures -- we ended up with a quiz full of questions. Finally, after the load, you do whatever it is that the user asked you to do. How do you know what to do? Why by looking in those freshly populated data structures.


==== Some popular Ruby DSLs ====  
==== Some popular Ruby DSLs ====  
Line 250: Line 134:
*'''Sinatra :'''[ http://www.sinatrarb.com/]
*'''Sinatra :'''[ http://www.sinatrarb.com/]
*'''Twibot :''' [ https://github.com/cjohansen/twibot/tree/master]
*'''Twibot :''' [ https://github.com/cjohansen/twibot/tree/master]
*'''Rails '''
**routes.rb
**migrations
*'''Rspec'''


== Advantages and Disadvantages ==
== Advantages and Disadvantages ==
Line 268: Line 156:


== Further Reading ==
== Further Reading ==
* A very comprehensive DSL example can be found here : [http://www.jroller.com/rolsen/entry/building_a_dsl_in_ruby Building DSL]
* [http://martinfowler.com/dsl.html Martin Fowler on domain-specific languages]
* [http://martinfowler.com/dsl.html Martin Fowler on domain-specific languages]
* [https://www.youtube.com/watch?v=QEZVpY_PCOY How to design a Domain Specific Language]
* [https://www.youtube.com/watch?v=QEZVpY_PCOY How to design a Domain Specific Language]


== References ==
== References ==
*[http://jroller.com/rolsen/entry/building_a_dsl_in_ruby DSL in Ruby]
 
*[http://www.javaworld.com/javaworld/jw-06-2008/jw-06-dsls-in-java-1.html?page=1 Building DSL in Java]
*[http://www.javaworld.com/javaworld/jw-06-2008/jw-06-dsls-in-java-1.html?page=1 Building DSL in Java]
*[http://en.wikipedia.org/wiki/Domain-specific_language Domain Specific Languages]
*[http://en.wikipedia.org/wiki/Domain-specific_language Domain Specific Languages]
*[http://softwarebyjosh.com/2012/01/08/How-To-Write-Your-Own-DSL.html How to write your own DSL]
*[http://softwarebyjosh.com/2012/01/08/How-To-Write-Your-Own-DSL.html How to write your own DSL]
*[http://raycoding.net/2013/08/11/writing-domain-specific-langaugedsl-in-ruby-day-1/ DSL Coding]
*[http://www.slideshare.net/ThoughtWorks0ffshore/dsl-construction-rith-ruby DSL construction with Ruby]

Latest revision as of 22:58, 23 September 2013

A domain specific language (DSL) is a computer programming language of limited expressiveness focused on a particular domain. A domain-specific language is created specifically to solve problems in a particular domain and is not intended to be able to solve problems outside it (although that may be technically possible).

Why use DSL ?

DSLs are useful tools – they use the grammar and syntax that closely resembles the lexicon used by the target domain so we can easily express logic specific to the particular problem.They have very specific business domain based implementations. It is designed to be very intuitive and fluent for a domain expert to use.A domain-specific language is similar to programming library and is somewhere between a tiny programming language and a scripting language.

DSLs are popular for two major reasons: improving productivity for developers and improving communication with domain experts.A well-built DSL can make it easier for the developers to understand a complicated workflow thereby increasing their productivity. The communication with the domain experts becomes easier as it provides a bridge between the development of an executable software and a description that the domain experts can understand to know how their requirements have been implemented in the system. This helps to address one of the major concerns in software development- the communication between the developers and the customers.

Lets take for example, a mathematician working on a particular algorithms does not think in loops, iterations and variables but instead he thinks in the form of base cases, recursions and base comparisons. Using a programming or a general purpose language with iterators and loops require him to mentally map the corresponding parameters from his domain to the language he wants to write his code in. Using A DSL for algorithm design can help nullify this translation and provide a much more effective and reusable code library.

Types of DSL

There are two types of DSLs namely Internal and External.

Internal DSL

An internal or embedded DSL is designed and implemented using a host language. A script in an internal DSL is valid code in its general-purpose language, but only uses a subset of the language's features in a particular style to handle one small aspect of the overall system.The programmer need not worry about grammar, parsers, and tools to do the heavy lifting. However, an internal DSL is constrained by the host language, and your DSL is influenced by its host's flexibility, limitations, and idiosyncrasies. The challenge with an internal DSL is to tactfully design the language so that the syntax is within the confines of what the host language allows, yet is as expressive, concise, and fluent as you desire.Ruby has also developed a strong DSL culture: Many Ruby libraries come in the style of DSLs. In particular, Ruby's most famous framework, Rails, is often seen as a collection of DSLs.

Advantages and Disadvantages of Internal DSLs

An internal DSL rides on the syntax of a host language, so the programmer need not spend any time or effort worrying about compiling or parsing. Also as the programmer is largely constrained by the host language, a lot of time is saved designing the syntax of the DSL.or an effective implementation of a DSL a flexible language with great metaprogramming capabilities would be useful.It is better to choose a programming languages which has few restrictions and idiosyncrasies.External DSLs give you better control than internal DSLs when validating DSL syntax. Because you take the effort to define the grammar for your external DSL, that effort also serves to validate the syntax. This is harder to do with an internal DSL because the code is often processed dynamically. You will have to do extensive error checking and do the validation yourself.

External DSL

An external free-standing DSL is a language separate from the main language of the application it works with. Usually, an external DSL has a custom syntax and programmer defined rules for grammar-definitions and parsing the syntax.It is upto the DSL programmer to decide on the language and the tools to implement the DSL. However, using another language's syntax like XML is also common. A script in an external DSL will usually be parsed by a code in the host application using text parsing techniques. Examples of external DSLs that you probably have come across include regular expressions, SQL and XML configuration files for systems like Struts and Hibernate.

Advantages and Disadvantages of External DSLs

An external DSL gives the programmer the liberty to design the syntax of his/her language in exactly the way desired. They can select the language's symbols, operators, constructs, and structure as they fit their domain. On the downside, the programmer has to define the grammar for their language and create a compiler to parse and process the syntax and map it to the semantics they expect. An external DSL gives a programmer a lot of flexibility, but you have to take the time to do the hard work of compiling it.

Building a DSL in Ruby

DSls are quite popular in Ruby because of the nature of the Ruby language itself:

  • Especially the ability of Ruby to use minimal syntax in order to make the code concise.
  • Ruby being a very expressive language and having readable syntax facilitates writing of DSLs where one has to come up with their own syntax.
  • Most ruby programs are already a DSL because of Ruby's syntax.


Below is the actual Ruby code:

it "must not allow a person to login without the proper credentials " do
 person.logs_in_with("invalid credentials");
 person.must_not(be(person.logged_in?()));
end

The above code can also be written as a DSL :

it "must not allow a person to login without the proper credentials " do
 person.logs_in_with :invalid credentials
 person.should_not_be_logged_in
end

From the above example we can clearly see that the DSL code is more readable and clearly explains what the code means. Notice how we used symbols(:invalid credentials) instead of strings to make it more readable.

How to build a DSL in ruby

A general process for writing a DSL in Ruby is as follows :

  • The process identification page involves identifying all the processes and functionality to be built into the DSL.
  • The second step involves choosing a desired syntax for writing the DSL.
  • In the third step we define the interface of the DSL or the API.
  • Next we define the classes and various abstraction put in place.
  • Finally we implement all the validations.

Good practice for building a Ruby DSL

Follow these good design principles to make a better DSL:

  • Always make entities as classes , so that they may correspond to a noun or a realworld entity.
  • All operations should be methods, typically use verbs to define an action.
  • Use in class definitions as much as possible.

Examples

Here are some examples that will explain the various benefits/use cases of DSLs in Ruby:

Internal DSL in Ruby

  • A programmer can create his/her own classes and define them:
class Integer
 def minutes
  .....
 end
end

60.minutes
20.mnutes.later
delete 20.minutes.later

We can see in the above code that the programmer has defined his own class Integer within it a method minutes. On the method he has applied various calls that would result in some action (in this case delete).

  • A DSL can be used to write more expressive code
every 3.day, :at => '2.00 pm' do
 delete
end

The above code easily reflects its purpose which is at a particular frequency and at a specific time carry out some action.

  • Following is an RSpec code example :
describe Group do
 it "name XYX " do
  x = Group.new
  x.should_not_be_valid
 end
end
  • A rails migration is also a DSL
create_table:employees do |x|
  x.string :name
  x.timestamps
end

Notice that in all the above examples the focus is on using your own syntax in making the code concise and easier to use. Herein lies the power of DSL.

Some popular Ruby DSLs

Some popular examples of Ruby DSLs are as follows:

Advantages and Disadvantages

Some of the advantages:

  • Domain-specific languages allow solutions to be expressed in the idiom and lexicon of the domain.It is possible for the domain experts to themselves define , modify and develop the domain-specific language code.
  • It is possible to do validations in DSL’s at the domain level.
  • The code in most cases is self-documented
  • Domain-specific languages improve quality, productivity, reliability, maintainability, portability and reusability.

Some of the disadvantages:

  • There are a lot of overhead costs involved w.r.t developing DSL’s.
  • The programmer needs to learn a new language , design, implement, setting and maintaining a scope and maintain a DSL as well as the tools required to develop it.
  • Also limited number of experts in a DSL increases the labor costs.
  • DSL’s also cause some level of processor efficiency hit as compared to hand-coded software.
  • Difficulty of balancing trade-offs between domain-specificity and general-purpose programming language constructs.
  • Integration of the DSL with other components of the IT system may be difficult.
  • Proliferation of similar non-standard domain-specific languages.Non-technical domain experts can find it hard to write or modify DSL programs by themselves.

Further Reading

References