Open In App

Domain-Specific Languages in Scala

Improve
Improve
Like Article
Like
Save
Share
Report

A Domain-Specific Language(DSL) is a computer language, specialized for a specific domain. A DSL is essentially the opposite of a general programming language (GPL). With a DSL, a problem from only specific domains can be solved, so we design the language specifically for the domain. A DSL can’t be used outside of the domain, because it is not designed for flexibility. The main advantage of creating DSLs is it will improve readability and reduce boilerplate code.

Features of DSL

1. Appropriate abstraction level for domain uses precise concepts and semantics of domain.
2. It is concise and expressive only for a specific domain; It is not used for general purposes.
3. The domain experts can communicate and critique better with programmers and among themselves.

Some well-known techniques DSLs in Scala

1. Vanishing method calling: Methods can be called without any name that is ‘vanishing’. In Scala, it is not required to name apply() or update() methods. Let us see the syntax for it.

##sample code for showing vanishing method

dtf (“sequence”) == dtf.apply(“sequence”) == dtf.column (“sequence”)

dtf (“sequence”) = dtf (“sequence”) + 1

dtf.update (dtf (“sequence”), dtf(“sequence”) + 1)

In the above code, apply() and Update() method is used on a random data frame that contains the ‘sequence’ column. The above code generates the next sequence as an increment.

2. Use nothing for operators: In Scala, methods look like operators because the ‘dot’ is optional. Any symbol can be used by the programmers as an operator. For example, if we want to list the students to check who takes admission to the Science dept listing can be done as follows.

stu.where(stu(“marks”).gt(75)) ## listing students who are getting 75% above marks

stu where (stu(“marks”)>75) ## syntax without any dots

Moreover, operators ending with a colon apply to the right operand i.e. it provides more freedom to the developers.

# sample code for syntax only

object magazine{

def read (who: String): Unit = println(s”$who is reading the magazine”) }

“Thomas” read_: magazine

3. Optimized uses of syntaxes: There are no requirements for unwanted commas and also it has more intuitive parentheses.

Like we can use when(2==2) {println(“4”)} instead of when (2==2, println(“4”)).

Classification of DSL:

  • Internal DSL- It is embedded by the host languages like java, Scala, ruby, etc., and also bounded by the host language syntax and semantics.
  • External DSL- It is a standalone development ground that has its own defined syntax and semantics as grammar. It uses some tools like lexical analyzers, parsers, interpretation, and code generators.

Internal DSLs in Scala 

There are some types of internal DSLs present in the Scala programming language. We will see them one by one-

1. Implicit conversations: It can be used here to prevent an error in data types because it allows the compiler to convert the data type from one to another. Implicit conversion from type ‘A’ to type ‘B’ is defined by either an implicit class ‘B’ that has a single parameter of type ‘A’, an implicit value that has function type A => B, or by an implicit method convertible to a value of that type.

2. Anonymous Functions: They play a very important role in any programming language. In Scala, we can use anonymous functions in Internal DSLs. In the following code example, the object named ‘GFG’ with the ‘main’ function contains two anonymous functions. They are syntactically different but can produce the same result, where ‘anomFunc‘ evaluates by taking parameters from a function call as it is being called, passing the input values, which results in output to be printed whereas ‘anomFuncTyp‘ is also called passing input values where it receives the value once, which accepts any valid ‘double’.

Scala




// Scala program for anonymous function
object GFG
{
    def main(args: Array[String]) 
    {
       
        // Anonymous functions
        var anomFunc = (x:Double, y:Double) => x + y
        var anomFuncTyp = (_:Double) + (_:Double)
       
        // Function calling
        println(anomFunc(20.55, 18.45))
        println(anomFuncTyp(5.95, 33.05))
    }
}


Output

39.0
39.0

3. Pattern Matching and Case Classes: They are also used for building internal DSLs. Pattern matching can include value, types, wild cards, tuples, sequences, etc. Case classes are used to make the code block construction simplified. By using pattern matching on Case classes we can develop a powerful DSL.

Pattern matching Example: Here we will see how we can define some cases and retrieve the data from them. Here, we will first define some matching cases, and from that matching case, we will retrieve data from the test case. Whenever the match case and test case value will be matched it will show the value of that case.

Scala




// Scala program for pattern matching
 
// Importing scala utilities
import scala.util.Random
 
val y: Int = Random.nextInt(10)
 
// Defining matching cases
y match {
  case 0 => "gfg"
  case 1 => "GFG"
  case 2 => "geeksforgeeks"
  case _ => "others"
}
 
// Defining a pattern matching test-case
def matchTest(y: Int): String = y match {
  case 0 => "gfg"
  case 1 => "GFG"
  case _ => "others"
}
 
// retrieve the particular case
println(matchTest(1))


Output:

GFG

Case classes example: Here we will define classes as cases and pass the parametric values to print them. In this code, we will first define an extensible class(GFGgoodies) and then case classes will be defined that extend GFGgoodies. Then a function(gettingswags) will be declared that contains the match cases constructed with case classes. Finally, we will print the results by function calls. 

Scala




// Scala program for case classes
 
// Defining a extensible trait or class
sealed trait GFGgoodies
 
// Defining case classes
case class tshirt( title: String,sender: String, body: String) extends GFGgoodies
case class gfgBag(title: String,sender: String) extends GFGgoodies
 
// Defining matching cases within a function
def gettingswags(swags: GFGgoodies): String = {
  swags match {
    case tshirt( title,sender, body) =>
      s"$title : You got an t-shirt from $sender. $body"
    case gfgBag(title, sender) =>
      s"$title: You got an bag for 10+ article from $sender  !"
  }
}
 
// Declaring variables
val goddie1 = tshirt("Congrats", "GeeksforGeeks", "Please give shipping details")
val goddie2 = gfgBag("Congrats", "Geeksforgeeks")
 
// Function calling
println(gettingswags(goddie1))
println(gettingswags(goddie2))


Output:

Congrats : You got an t-shirt from GeeksforGeeks. Please give shipping details
Congrats: You got an bag for 10+ article from Geeksforgeeks  !

External DSL in Scala

For external DSL Parser combinator library is used which is available as a library in Scala. External parser generators are used to generate code for tokenizing and parsing. BNF grammar is used as a Parser combinator Specification.

Some more features:

  • Each function works as a parser here. It works on a portion of the input data and parses it. Optionally it may pass on the remaining amount to the next parser in the chain via a combinator.
  • The sequencing combinator(~) is used to compose two parsers sequentially.
  • To apply the function on the result of the sequential combinator Functional Application combinator(^^) can be used.


Last Updated : 19 Jan, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads