SlideShare a Scribd company logo
Building DSLs
With Scala
github.com/alonmuch@WixEngalonmu@wix.com
Alon Muchnick
When you order
coffee, what do
you ask for?
Once a language’s
corner stones are
established,
its common vocabulary
can be expanded.
Introduction to
DSLs
01
A domain-specific language (DSL)
is a computer language specialized
to a particular application domain.“ “
DSLs are made to fit
their purpose only.
How is DSL different from any other
programming language?
● It is targeted at a specific problem domain
● It offers a higher level of abstraction to the user
Coffee DSL
SQL
CSS
Simple mapping is the challenge…
Problem
Domain
Solution
Implementation
Essential Complexity Accidental Complexity
● Sending a rocket to space
● Building a search engine
● Supporting high load of traffic
● Writing unclear code
● Over engineering
● Using the wrong tool for the job
DSLs reduces the accidental complexity
DSLs reduces the accidental complexity
Why Scala ?
02
Q&A
Flexible syntax, fit for abstraction
"123." contains(" 2)"<==>"123sniatnoc" ( "2" )
val a = 2<==>lav= a2 ;
"123." contains(" 2)"<==>"123sniatnoc"2" "
val name = "scala" >==< val name: String = "scala"
Optional dots in
method invocation
Semicolon
inference
Optional
parentheses
Type
inference
Q&A
We can easily pass complex logic
using simple expressions
Concise lambda
syntax
numbers map { x => x+1}
numbers sortWith { (x,y) => x>y }
numbers map {_+1} sortWith { _>_ }
Q&A
Case classes - perfect for abstraction design
case class Name (
val first: String = "hello",
val last: String = "world"
)
val myName = Name)first = "James ", last = "Bond")
Scala Implicits
● provide an implicit conversion from type to type
● statically typed
● allow us to create Lexically scoped open classes
Scala Implicits params example
object Main extends App {
def sayHello(name: String)(implicit greeting: String) {
println(s"$greeting, $name")
}
implicit val greeting = "Hi"
sayHello("George")
}
Scala Implicits conversion
def intOnly(int: Int) = {
. . .
}
intOnly(1.1)
“Type mismatch, expected Int actual Double”
Scala Implicits conversion
def intOnly(int: Int) = {
. . .
}
intOnly(1.1)
implicit def double2int(x: Double): Int = x.toInt
Scala open classes
Map(1 -> "one", 2 -> "two", 3 -> "three")
implicit final class ArrowAssoc[A](self : A) {
@scala.inline
def ->[B](y : B) : scala.Tuple2[A, B] = { /* compiled code */
def →[B](y : B) : scala.Tuple2[A, B] = { /* compiled code */ }
}
Lets build a
DSL !
03
We are building a geo-based tax rule
matching system for shopping carts
In English, please
● “for Canada the tax is 15% of cart total"
● “for UK the tax is 12% of cart total, when calculating the tax ignore the shipping cost”
● “for USA the tax is 7% of cart total, when calculating the tax ignore the shipping cost and the discount”
● “for Finland the tax is 5% of cart total, when calculating the tax ignore discount if shipping costs larger than
4€"
● “for Israel the tax is 25% of cart total, add a special custom tourist tax”
We will want to translate this to a list of requirements
● The tax for a shopping cart is calculated by the shipping country
● Tax is different for each country
● Tax calculation is depended on other factor like shipping and
discount
System requirements
● Clear communication between project teams
● Tests speak the same language and can be reviewed by non
technical personal
Benefits of a common vocabulary
Break it down
● “for Canada the tax is 15% of cart total ”
● “for UK the tax is 12% of cart total, when calculating the tax ignore the shipping cost”
● “for USA the tax is 7% of cart total , when calculating the tax ignore the shipping cost and the
discount ”
● “for Finland the tax is 5% of cart total , when calculating the tax ignore discount if shipping costs
larger than 4 €”
● “for Israel the tax is 25% of cart total, add a special custom tourist tax”
● Tax is calculated on a cart
● Cart is shipped to a specific country
● Cart might or might not have a discount ….
Understanding the relationship
Create domain abstractions
object Cart {
val shipping = (c: Cart) => c.shipping
val discount = (c: Cart) => c.discount
}
case class Cart( total: BigDecimal,
shipping: BigDecimal,
discount: BigDecimal,
country: Country)
Create domain abstractions
sealed trait Country
object Countries {
case object Israel extends Country
case object USA extends Country
case object FINLAND extends Country
case object UK extends Country
Case object CANADA extends Country
}
type CartPredicate = Cart => Boolean
type TaxCalculator = Cart => BigDecimal
type CountryToTaxCalculation = (Country, TaxCalculator)
“for Canada the tax is 15%”
def For(c: Country): CountryContainer = {
CountryContainer(c)
}
case class CountryContainer(country: Country) {
def take(tax: BigDecimal): CountryToTaxCalculation = {
(country, (cart: Cart) => cart.total * tax)
}
}
“for Canada the tax is 15%”
For(CANADA).take(0.15)
For(CANADA) take 0.15
whats going on under the hood ?
For(Canada) take 0.15
CountryContainer
(returns) (invokes)
(returns)
CountryToTaxCalculation
“for UK the tax is 12%, ignore the shipping costs”
Already supported
No!
Can we extend our solution
to support this part?
Next up
Add an intermediate step when needed
case class CountryContainer(country: Country) {
def take(tax: BigDecimal): CountryToTaxCalculation = {
(country, (cart: Cart) => cart.total * tax)
}
}
old
case class CountryContainer(country: Country) {
def take(tax: BigDecimal): TaxAndCountry =
TaxAndCountryContainer(country, tax)
}
new
Extend the vocabulary when needed
case class TaxAndCountryContainer
(country: Country, tax: BigDecimal) {
}
def ignore(cartField: CartField): CountryToTaxCalculation =
(country, (c: Cart) => (c.total - cartField(c)) * tax)
For(UK).take(0.12).ignore(shipping)
For(UK) take 0.12 ignore shipping
“for UK the tax is 12%, ignore the shipping costs”
What's going on under the hood ?
For(UK) take 0.12 ignore shipping
CountryContainer
(returns) (invokes)
(returns)
TaxAndCountry
Container
(invokes)
(returns)
CountryToTaxCalculation
will “for Canada the tax is 15%” still work ??
For(CANADA) take 0.17
CountryContainer
(invokes)(returns)
(returns)
TaxAndCountryContainer != CountryToTaxCalculation
Fix when needed
implicit def taxAndCountryToTaxCalculation
(taxAndCountry: TaxAndCountry): CountryToTaxCalculation = {
(taxAndCountry.country,
{ (cart: Cart) => (cart.total * taxAndCountry.tax) })
}
What's going on under the hood ?
For(CANADA) take 0.17
CountryContainer
(invokes)(returns)
(returns)
TaxAndCountry
(implicit conversion)
CountryToTaxCalculation
Workflow
Break the English
rules down
Understand the
relationships
Create domain
abstractions
Implement logic
structures
When needed:
• Add an intermediate step
• Extend the vocabulary
• Fix previous abstractions
Can we improve the ignore function
to take multiple params?
“for USA the tax is 12%, when calculating the tax ignore the shipping cost
and the discount ”
Next up
TaxAndCountry
def ignore(cartField: CartField): CountryToTaxCalculation =
(country, (c: Cart) => (c.total - cartField(c)) * tax)
old
def ignore(cartFields: CartField*): CountryToTaxCalculation =
(country, (c: Cart) =>
(c.total - cartFields.foldLeft(BigDecimal(0))((a, f) => a + f(c))) * tax)
new
“for USA the tax is 12%, when calculating the tax ignore the shipping cost
and the discount ”
For(USA).take(0.12).ignore(discount, shipping)
We can do better...
taking it to the next level
object Cart {
val shipping = (c: Cart) => c.shipping
val discount = (c: Cart) => c.discount
}
object Cart {
val shipping = new CartCombinator((c: Cart) => c.shipping)
val discount = new CartCombinator((c: Cart) => c.discount)
}
CartCombinator
case class CartCombinator(val cartField: CartField) {
def and (comb: CartCombinator): CartCombinator =
new CartCombinator((c: Cart) => comb.cartField(c) + cartField(c))
}
TaxAndCountry – with combinators
case class TaxAndCountry(country: Country, tax: BigDecimal) {
def ignore(combs: CartCombinator*): CountryToTaxCalculation = {
def calculateTax(cart: Cart): BigDecimal =
(cart.total - combineAllRules(cart)) * tax
def combineAllRules(cart: Cart): BigDecimal =
combs.foldLeft(BigDecimal(0))((a, b) => a + b.cartField(cart))
(country, calculateTax)
}
“for USA the tax is 7%, when calculating the tax ignore the shipping cost
and the discount ”
For(USA).take(0.07).ignore(discount and shipping)
For(USA) take 0.07 ignore discount and shipping
Let’s add the & operator
class CartCombinator(val cartField: CartField) {
def and (comb: CartCombinator): CartCombinator =
new CartCombinator((c: Cart) => comb.cartField(c) + cartField(c))
def & (occ: CartCombinator): CartCombinator = and(occ)
}
“for USA the tax is 7%, when calculating the tax ignore the shipping cost
and the discount ”
For(USA).take(0.07).ignore(discount and shipping)
For(USA) take 0.07 ignore discount & shipping
operator precedence by order
(all letters)
|
^
&
< >
= !
:
+ -
* / %
(all other special characters)
What's going on under the hood ?
For(USA) take 0.07 ignore discount & shipping
CountryContainer
(invokes)(returns)
(returns)
TaxAndCountry
container
(invokes)
(returns)
CountryToTaxCalculation
(returns)
CartCombinator
Supported rules so far
For(CANADA) take 0.15
For(UK) take 0.12 ignore shipping
For(USA) take 0.07 ignore discount & shipping
“for Finland the tax is 5%, when calculating the tax ignore discount
if shipping costs larger than 4”
Next up
Unsupported
adding a predicate
class CartCombinator(val cartField: CartField) {
def If (cond: CartPredicate): CartCombinator =
new CartCombinator((c: Cart) =>
if (cond(c)) cartField(c) else 0)
def > (value: BigDecimal): CartPredicate =
((c: Cart) => cartField(c) > value)
. . .
adding a predicate
class CartCombinator(val cartField: CartField) {
def and (comb: CartCombinator): CartCombinator =
new CartCombinator((c: Cart) => comb.cartField(c) + cartField(c))
def & (occ: CartCombinator): CartCombinator = and(occ)
def If (cond: CartPredicate): CartCombinator =
new CartCombinator((c: Cart) =>
if (cond(c)) cartField(c) else 0)
def > (value: BigDecimal): CartPredicate =
((c: Cart) => cartField(c) > value)
}
“for Finland the tax is 5%, when calculating the tax ignore discount if
shipping costs larger than 4 ”
For(FINLAND).take(0.05).ignore (discount.If(shipping > 4))
For(FINLAND) take 0.05 ignore (discount If shipping > 4)
What's going on under the hood ?
For(FINLAND) take 0.05 ignore (discount If shipping > 4)
(invokes)
(returns)
CountryToTaxCalculation
CartCombinator
(returns)
CountryContainer
(invokes)(returns)
TaxAndCountry
container
(returns)
Adding custom values
case class TaxAndCountry(country: Country, tax: BigDecimal) {
def addTouristPrice (f: Cart => BigDecimal): CountryToTaxCalculation =
addCustomValue(f)
private def addCustomValue (f:CartField): CountryToTaxCalculation =
(country, (cart: Cart) => (cart.total + f(cart)) * tax)
def ignore(fs: CartCombinator*): CountryToTaxCalculation = {
. . .
What's going on under the hood ?
For(Israel) take 0.25 addTouristPrice { cart => cart.total * 0.2}
CountryContainer
(invokes)(returns)
(returns)
TaxAndCountry
container
(invokes) (returns)
CountryToTaxCalculation
Tax Rules DSL
val taxRules: Seq[CountryToTax] = Seq(
For(CANADA) take 0.15 ,
For(UK) take 0.12 ignore shipping,
For(USA) take 0.07 ignore discount & shipping,
For(FINLAND) take 0.05 ignore (discount If shipping > 4),
For(Israel) take 0.25 addTouristPrice{ cart => cart.total * 0.2 }
)
English DSL
● “for Canada the tax is 15%" For(CANADA) take 0.15
● “for UK the tax is 12%, when calculating
the tax ignore the shipping cost”
For(UK) take 0.12 ignore shipping
● “for the USA the tax is 7%, ignore the
shipping cost and the discount"
For(USA) take 0.07
ignore discount & shipping
● “for Finland the tax is 5% ignore discount
if shipping costs larger than 4”
For(FIN) take 0.05
ignore (discount If shipping > 4)
● “for Israel the tax is 25%, add a special
custom tourist tax just for fun”
For(Israel) take 0.25
addTouristPrice {cart => cart.total * 0.2}
Matching tax rule for a cart
import TaxDsl._
val taxRules: Seq[CountryToTax]
val cart = Cart(total = 100, shipping = 10,discount =
20, country = USA)
val taxRule = taxRules findRuleFor cart
To activate this we need to
enrich an existing class
Enriching Seq
implicit class TaxRuleSeq(taxRules: Seq[CountryToTaxCalculation]) {
def findRuleFor(cart: Cart) = {
taxRules.find(_._1 == cart.country).map(_._2)
}
}
Errors and exceptions
● Always use the domain language
to express any exception that might occur during processing
● The compiler acts as the policeman for you
Final notes
● getting the syntax right is hard
● A DSL needs only to be expressive enough for the user
● DSLs are fun !
Q&A
This is where you are going to present your final words.
This slide is not meant to have a lot of text.Thank You!
Any Questions?
Alon Muchnick
@WixEngalonmu@wix.com

More Related Content

Similar to Building DSLs with Scala

Kotlin dsl
Kotlin dslKotlin dsl
Jetpack Compose - A Lightning Tour
Jetpack Compose - A Lightning TourJetpack Compose - A Lightning Tour
Jetpack Compose - A Lightning Tour
Matthew Clarke
 
Joy of scala
Joy of scalaJoy of scala
Joy of scala
Maxim Novak
 
Apache Spark for Library Developers with William Benton and Erik Erlandson
 Apache Spark for Library Developers with William Benton and Erik Erlandson Apache Spark for Library Developers with William Benton and Erik Erlandson
Apache Spark for Library Developers with William Benton and Erik Erlandson
Databricks
 
DSL in scala
DSL in scalaDSL in scala
DSL in scala
Xuân Thu Nguyễn
 
The Kokkos C++ Performance Portability EcoSystem
The Kokkos C++ Performance Portability EcoSystemThe Kokkos C++ Performance Portability EcoSystem
The Kokkos C++ Performance Portability EcoSystem
inside-BigData.com
 
Billing in a supermarket c++
Billing in a supermarket c++Billing in a supermarket c++
Billing in a supermarket c++
varun arora
 
Scala to assembly
Scala to assemblyScala to assembly
Scala to assembly
Jarek Ratajski
 
The C# programming laguage delegates notes Delegates.pptx
The C# programming laguage delegates notes Delegates.pptxThe C# programming laguage delegates notes Delegates.pptx
The C# programming laguage delegates notes Delegates.pptx
VitsRangannavar
 
FOIAfest 2016 Session: Data crunchers, this session is for you
FOIAfest 2016 Session: Data crunchers, this session is for youFOIAfest 2016 Session: Data crunchers, this session is for you
FOIAfest 2016 Session: Data crunchers, this session is for you
Smart Chicago Collaborative
 
CppTutorial.ppt
CppTutorial.pptCppTutorial.ppt
CppTutorial.ppt
HODZoology3
 
Visualizing Large Greenhouse Gas Datasets in the Browser With deck.gl
Visualizing Large Greenhouse Gas Datasets in the Browser With deck.glVisualizing Large Greenhouse Gas Datasets in the Browser With deck.gl
Visualizing Large Greenhouse Gas Datasets in the Browser With deck.gl
All Things Open
 
Cpp tutorial
Cpp tutorialCpp tutorial
Cpp tutorial
Vikas Sharma
 
INSTRUCTIONS Please organize your answers as a Word document with.docx
INSTRUCTIONS Please organize your answers as a Word document with.docxINSTRUCTIONS Please organize your answers as a Word document with.docx
INSTRUCTIONS Please organize your answers as a Word document with.docx
dirkrplav
 
Receipt processing with Google Cloud Platform and the Google Assistant
Receipt processing with Google Cloud Platform and the Google AssistantReceipt processing with Google Cloud Platform and the Google Assistant
Receipt processing with Google Cloud Platform and the Google Assistant
Orestes Carracedo
 

Similar to Building DSLs with Scala (15)

Kotlin dsl
Kotlin dslKotlin dsl
Kotlin dsl
 
Jetpack Compose - A Lightning Tour
Jetpack Compose - A Lightning TourJetpack Compose - A Lightning Tour
Jetpack Compose - A Lightning Tour
 
Joy of scala
Joy of scalaJoy of scala
Joy of scala
 
Apache Spark for Library Developers with William Benton and Erik Erlandson
 Apache Spark for Library Developers with William Benton and Erik Erlandson Apache Spark for Library Developers with William Benton and Erik Erlandson
Apache Spark for Library Developers with William Benton and Erik Erlandson
 
DSL in scala
DSL in scalaDSL in scala
DSL in scala
 
The Kokkos C++ Performance Portability EcoSystem
The Kokkos C++ Performance Portability EcoSystemThe Kokkos C++ Performance Portability EcoSystem
The Kokkos C++ Performance Portability EcoSystem
 
Billing in a supermarket c++
Billing in a supermarket c++Billing in a supermarket c++
Billing in a supermarket c++
 
Scala to assembly
Scala to assemblyScala to assembly
Scala to assembly
 
The C# programming laguage delegates notes Delegates.pptx
The C# programming laguage delegates notes Delegates.pptxThe C# programming laguage delegates notes Delegates.pptx
The C# programming laguage delegates notes Delegates.pptx
 
FOIAfest 2016 Session: Data crunchers, this session is for you
FOIAfest 2016 Session: Data crunchers, this session is for youFOIAfest 2016 Session: Data crunchers, this session is for you
FOIAfest 2016 Session: Data crunchers, this session is for you
 
CppTutorial.ppt
CppTutorial.pptCppTutorial.ppt
CppTutorial.ppt
 
Visualizing Large Greenhouse Gas Datasets in the Browser With deck.gl
Visualizing Large Greenhouse Gas Datasets in the Browser With deck.glVisualizing Large Greenhouse Gas Datasets in the Browser With deck.gl
Visualizing Large Greenhouse Gas Datasets in the Browser With deck.gl
 
Cpp tutorial
Cpp tutorialCpp tutorial
Cpp tutorial
 
INSTRUCTIONS Please organize your answers as a Word document with.docx
INSTRUCTIONS Please organize your answers as a Word document with.docxINSTRUCTIONS Please organize your answers as a Word document with.docx
INSTRUCTIONS Please organize your answers as a Word document with.docx
 
Receipt processing with Google Cloud Platform and the Google Assistant
Receipt processing with Google Cloud Platform and the Google AssistantReceipt processing with Google Cloud Platform and the Google Assistant
Receipt processing with Google Cloud Platform and the Google Assistant
 

Recently uploaded

Going AOT: Everything you need to know about GraalVM for Java applications
Going AOT: Everything you need to know about GraalVM for Java applicationsGoing AOT: Everything you need to know about GraalVM for Java applications
Going AOT: Everything you need to know about GraalVM for Java applications
Alina Yurenko
 
42 Ways to Generate Real Estate Leads - Sellxpert
42 Ways to Generate Real Estate Leads - Sellxpert42 Ways to Generate Real Estate Leads - Sellxpert
42 Ways to Generate Real Estate Leads - Sellxpert
vaishalijagtap12
 
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
dakas1
 
Assure Contact Center Experiences for Your Customers With ThousandEyes
Assure Contact Center Experiences for Your Customers With ThousandEyesAssure Contact Center Experiences for Your Customers With ThousandEyes
Assure Contact Center Experiences for Your Customers With ThousandEyes
ThousandEyes
 
Operational ease MuleSoft and Salesforce Service Cloud Solution v1.0.pptx
Operational ease MuleSoft and Salesforce Service Cloud Solution v1.0.pptxOperational ease MuleSoft and Salesforce Service Cloud Solution v1.0.pptx
Operational ease MuleSoft and Salesforce Service Cloud Solution v1.0.pptx
sandeepmenon62
 
Modelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - AmsterdamModelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - Amsterdam
Alberto Brandolini
 
14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
ShulagnaSarkar2
 
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
gapen1
 
Alluxio Webinar | 10x Faster Trino Queries on Your Data Platform
Alluxio Webinar | 10x Faster Trino Queries on Your Data PlatformAlluxio Webinar | 10x Faster Trino Queries on Your Data Platform
Alluxio Webinar | 10x Faster Trino Queries on Your Data Platform
Alluxio, Inc.
 
DECODING JAVA THREAD DUMPS: MASTER THE ART OF ANALYSIS
DECODING JAVA THREAD DUMPS: MASTER THE ART OF ANALYSISDECODING JAVA THREAD DUMPS: MASTER THE ART OF ANALYSIS
DECODING JAVA THREAD DUMPS: MASTER THE ART OF ANALYSIS
Tier1 app
 
How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?
ToXSL Technologies
 
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
kalichargn70th171
 
Migration From CH 1.0 to CH 2.0 and Mule 4.6 & Java 17 Upgrade.pptx
Migration From CH 1.0 to CH 2.0 and  Mule 4.6 & Java 17 Upgrade.pptxMigration From CH 1.0 to CH 2.0 and  Mule 4.6 & Java 17 Upgrade.pptx
Migration From CH 1.0 to CH 2.0 and Mule 4.6 & Java 17 Upgrade.pptx
ervikas4
 
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
XfilesPro
 
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
kgyxske
 
ACE - Team 24 Wrapup event at ahmedabad.
ACE - Team 24 Wrapup event at ahmedabad.ACE - Team 24 Wrapup event at ahmedabad.
ACE - Team 24 Wrapup event at ahmedabad.
Maitrey Patel
 
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdfBaha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
Baha Majid
 
🏎️Tech Transformation: DevOps Insights from the Experts 👩‍💻
🏎️Tech Transformation: DevOps Insights from the Experts 👩‍💻🏎️Tech Transformation: DevOps Insights from the Experts 👩‍💻
🏎️Tech Transformation: DevOps Insights from the Experts 👩‍💻
campbellclarkson
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
Drona Infotech
 

Recently uploaded (20)

Going AOT: Everything you need to know about GraalVM for Java applications
Going AOT: Everything you need to know about GraalVM for Java applicationsGoing AOT: Everything you need to know about GraalVM for Java applications
Going AOT: Everything you need to know about GraalVM for Java applications
 
42 Ways to Generate Real Estate Leads - Sellxpert
42 Ways to Generate Real Estate Leads - Sellxpert42 Ways to Generate Real Estate Leads - Sellxpert
42 Ways to Generate Real Estate Leads - Sellxpert
 
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
一比一原版(UMN毕业证)明尼苏达大学毕业证如何办理
 
Assure Contact Center Experiences for Your Customers With ThousandEyes
Assure Contact Center Experiences for Your Customers With ThousandEyesAssure Contact Center Experiences for Your Customers With ThousandEyes
Assure Contact Center Experiences for Your Customers With ThousandEyes
 
Operational ease MuleSoft and Salesforce Service Cloud Solution v1.0.pptx
Operational ease MuleSoft and Salesforce Service Cloud Solution v1.0.pptxOperational ease MuleSoft and Salesforce Service Cloud Solution v1.0.pptx
Operational ease MuleSoft and Salesforce Service Cloud Solution v1.0.pptx
 
Modelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - AmsterdamModelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - Amsterdam
 
14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
 
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
 
Alluxio Webinar | 10x Faster Trino Queries on Your Data Platform
Alluxio Webinar | 10x Faster Trino Queries on Your Data PlatformAlluxio Webinar | 10x Faster Trino Queries on Your Data Platform
Alluxio Webinar | 10x Faster Trino Queries on Your Data Platform
 
DECODING JAVA THREAD DUMPS: MASTER THE ART OF ANALYSIS
DECODING JAVA THREAD DUMPS: MASTER THE ART OF ANALYSISDECODING JAVA THREAD DUMPS: MASTER THE ART OF ANALYSIS
DECODING JAVA THREAD DUMPS: MASTER THE ART OF ANALYSIS
 
How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?
 
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
The Power of Visual Regression Testing_ Why It Is Critical for Enterprise App...
 
Migration From CH 1.0 to CH 2.0 and Mule 4.6 & Java 17 Upgrade.pptx
Migration From CH 1.0 to CH 2.0 and  Mule 4.6 & Java 17 Upgrade.pptxMigration From CH 1.0 to CH 2.0 and  Mule 4.6 & Java 17 Upgrade.pptx
Migration From CH 1.0 to CH 2.0 and Mule 4.6 & Java 17 Upgrade.pptx
 
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
Everything You Need to Know About X-Sign: The eSign Functionality of XfilesPr...
 
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
 
ACE - Team 24 Wrapup event at ahmedabad.
ACE - Team 24 Wrapup event at ahmedabad.ACE - Team 24 Wrapup event at ahmedabad.
ACE - Team 24 Wrapup event at ahmedabad.
 
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdfBaha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
 
🏎️Tech Transformation: DevOps Insights from the Experts 👩‍💻
🏎️Tech Transformation: DevOps Insights from the Experts 👩‍💻🏎️Tech Transformation: DevOps Insights from the Experts 👩‍💻
🏎️Tech Transformation: DevOps Insights from the Experts 👩‍💻
 
bgiolcb
bgiolcbbgiolcb
bgiolcb
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
 

Building DSLs with Scala

  • 2. When you order coffee, what do you ask for?
  • 3. Once a language’s corner stones are established, its common vocabulary can be expanded.
  • 5. A domain-specific language (DSL) is a computer language specialized to a particular application domain.“ “
  • 6. DSLs are made to fit their purpose only. How is DSL different from any other programming language? ● It is targeted at a specific problem domain ● It offers a higher level of abstraction to the user
  • 8. SQL
  • 9. CSS
  • 10. Simple mapping is the challenge… Problem Domain Solution Implementation
  • 11. Essential Complexity Accidental Complexity ● Sending a rocket to space ● Building a search engine ● Supporting high load of traffic ● Writing unclear code ● Over engineering ● Using the wrong tool for the job
  • 12. DSLs reduces the accidental complexity
  • 13. DSLs reduces the accidental complexity
  • 15. Q&A Flexible syntax, fit for abstraction "123." contains(" 2)"<==>"123sniatnoc" ( "2" ) val a = 2<==>lav= a2 ; "123." contains(" 2)"<==>"123sniatnoc"2" " val name = "scala" >==< val name: String = "scala" Optional dots in method invocation Semicolon inference Optional parentheses Type inference
  • 16. Q&A We can easily pass complex logic using simple expressions Concise lambda syntax numbers map { x => x+1} numbers sortWith { (x,y) => x>y } numbers map {_+1} sortWith { _>_ }
  • 17. Q&A Case classes - perfect for abstraction design case class Name ( val first: String = "hello", val last: String = "world" ) val myName = Name)first = "James ", last = "Bond")
  • 18. Scala Implicits ● provide an implicit conversion from type to type ● statically typed ● allow us to create Lexically scoped open classes
  • 19. Scala Implicits params example object Main extends App { def sayHello(name: String)(implicit greeting: String) { println(s"$greeting, $name") } implicit val greeting = "Hi" sayHello("George") }
  • 20. Scala Implicits conversion def intOnly(int: Int) = { . . . } intOnly(1.1) “Type mismatch, expected Int actual Double”
  • 21. Scala Implicits conversion def intOnly(int: Int) = { . . . } intOnly(1.1) implicit def double2int(x: Double): Int = x.toInt
  • 22. Scala open classes Map(1 -> "one", 2 -> "two", 3 -> "three") implicit final class ArrowAssoc[A](self : A) { @scala.inline def ->[B](y : B) : scala.Tuple2[A, B] = { /* compiled code */ def →[B](y : B) : scala.Tuple2[A, B] = { /* compiled code */ } }
  • 24. We are building a geo-based tax rule matching system for shopping carts
  • 25. In English, please ● “for Canada the tax is 15% of cart total" ● “for UK the tax is 12% of cart total, when calculating the tax ignore the shipping cost” ● “for USA the tax is 7% of cart total, when calculating the tax ignore the shipping cost and the discount” ● “for Finland the tax is 5% of cart total, when calculating the tax ignore discount if shipping costs larger than 4€" ● “for Israel the tax is 25% of cart total, add a special custom tourist tax” We will want to translate this to a list of requirements
  • 26. ● The tax for a shopping cart is calculated by the shipping country ● Tax is different for each country ● Tax calculation is depended on other factor like shipping and discount System requirements
  • 27. ● Clear communication between project teams ● Tests speak the same language and can be reviewed by non technical personal Benefits of a common vocabulary
  • 28. Break it down ● “for Canada the tax is 15% of cart total ” ● “for UK the tax is 12% of cart total, when calculating the tax ignore the shipping cost” ● “for USA the tax is 7% of cart total , when calculating the tax ignore the shipping cost and the discount ” ● “for Finland the tax is 5% of cart total , when calculating the tax ignore discount if shipping costs larger than 4 €” ● “for Israel the tax is 25% of cart total, add a special custom tourist tax”
  • 29. ● Tax is calculated on a cart ● Cart is shipped to a specific country ● Cart might or might not have a discount …. Understanding the relationship
  • 30. Create domain abstractions object Cart { val shipping = (c: Cart) => c.shipping val discount = (c: Cart) => c.discount } case class Cart( total: BigDecimal, shipping: BigDecimal, discount: BigDecimal, country: Country)
  • 31. Create domain abstractions sealed trait Country object Countries { case object Israel extends Country case object USA extends Country case object FINLAND extends Country case object UK extends Country Case object CANADA extends Country } type CartPredicate = Cart => Boolean type TaxCalculator = Cart => BigDecimal type CountryToTaxCalculation = (Country, TaxCalculator)
  • 32. “for Canada the tax is 15%” def For(c: Country): CountryContainer = { CountryContainer(c) } case class CountryContainer(country: Country) { def take(tax: BigDecimal): CountryToTaxCalculation = { (country, (cart: Cart) => cart.total * tax) } }
  • 33. “for Canada the tax is 15%” For(CANADA).take(0.15) For(CANADA) take 0.15
  • 34. whats going on under the hood ? For(Canada) take 0.15 CountryContainer (returns) (invokes) (returns) CountryToTaxCalculation
  • 35. “for UK the tax is 12%, ignore the shipping costs” Already supported No! Can we extend our solution to support this part? Next up
  • 36. Add an intermediate step when needed case class CountryContainer(country: Country) { def take(tax: BigDecimal): CountryToTaxCalculation = { (country, (cart: Cart) => cart.total * tax) } } old case class CountryContainer(country: Country) { def take(tax: BigDecimal): TaxAndCountry = TaxAndCountryContainer(country, tax) } new
  • 37. Extend the vocabulary when needed case class TaxAndCountryContainer (country: Country, tax: BigDecimal) { } def ignore(cartField: CartField): CountryToTaxCalculation = (country, (c: Cart) => (c.total - cartField(c)) * tax)
  • 38. For(UK).take(0.12).ignore(shipping) For(UK) take 0.12 ignore shipping “for UK the tax is 12%, ignore the shipping costs”
  • 39. What's going on under the hood ? For(UK) take 0.12 ignore shipping CountryContainer (returns) (invokes) (returns) TaxAndCountry Container (invokes) (returns) CountryToTaxCalculation
  • 40. will “for Canada the tax is 15%” still work ?? For(CANADA) take 0.17 CountryContainer (invokes)(returns) (returns) TaxAndCountryContainer != CountryToTaxCalculation
  • 41. Fix when needed implicit def taxAndCountryToTaxCalculation (taxAndCountry: TaxAndCountry): CountryToTaxCalculation = { (taxAndCountry.country, { (cart: Cart) => (cart.total * taxAndCountry.tax) }) }
  • 42. What's going on under the hood ? For(CANADA) take 0.17 CountryContainer (invokes)(returns) (returns) TaxAndCountry (implicit conversion) CountryToTaxCalculation
  • 43. Workflow Break the English rules down Understand the relationships Create domain abstractions Implement logic structures When needed: • Add an intermediate step • Extend the vocabulary • Fix previous abstractions
  • 44. Can we improve the ignore function to take multiple params? “for USA the tax is 12%, when calculating the tax ignore the shipping cost and the discount ” Next up
  • 45. TaxAndCountry def ignore(cartField: CartField): CountryToTaxCalculation = (country, (c: Cart) => (c.total - cartField(c)) * tax) old def ignore(cartFields: CartField*): CountryToTaxCalculation = (country, (c: Cart) => (c.total - cartFields.foldLeft(BigDecimal(0))((a, f) => a + f(c))) * tax) new
  • 46. “for USA the tax is 12%, when calculating the tax ignore the shipping cost and the discount ” For(USA).take(0.12).ignore(discount, shipping) We can do better...
  • 47. taking it to the next level object Cart { val shipping = (c: Cart) => c.shipping val discount = (c: Cart) => c.discount } object Cart { val shipping = new CartCombinator((c: Cart) => c.shipping) val discount = new CartCombinator((c: Cart) => c.discount) }
  • 48. CartCombinator case class CartCombinator(val cartField: CartField) { def and (comb: CartCombinator): CartCombinator = new CartCombinator((c: Cart) => comb.cartField(c) + cartField(c)) }
  • 49. TaxAndCountry – with combinators case class TaxAndCountry(country: Country, tax: BigDecimal) { def ignore(combs: CartCombinator*): CountryToTaxCalculation = { def calculateTax(cart: Cart): BigDecimal = (cart.total - combineAllRules(cart)) * tax def combineAllRules(cart: Cart): BigDecimal = combs.foldLeft(BigDecimal(0))((a, b) => a + b.cartField(cart)) (country, calculateTax) }
  • 50. “for USA the tax is 7%, when calculating the tax ignore the shipping cost and the discount ” For(USA).take(0.07).ignore(discount and shipping) For(USA) take 0.07 ignore discount and shipping
  • 51. Let’s add the & operator class CartCombinator(val cartField: CartField) { def and (comb: CartCombinator): CartCombinator = new CartCombinator((c: Cart) => comb.cartField(c) + cartField(c)) def & (occ: CartCombinator): CartCombinator = and(occ) }
  • 52. “for USA the tax is 7%, when calculating the tax ignore the shipping cost and the discount ” For(USA).take(0.07).ignore(discount and shipping) For(USA) take 0.07 ignore discount & shipping
  • 53. operator precedence by order (all letters) | ^ & < > = ! : + - * / % (all other special characters)
  • 54. What's going on under the hood ? For(USA) take 0.07 ignore discount & shipping CountryContainer (invokes)(returns) (returns) TaxAndCountry container (invokes) (returns) CountryToTaxCalculation (returns) CartCombinator
  • 55. Supported rules so far For(CANADA) take 0.15 For(UK) take 0.12 ignore shipping For(USA) take 0.07 ignore discount & shipping “for Finland the tax is 5%, when calculating the tax ignore discount if shipping costs larger than 4” Next up Unsupported
  • 56. adding a predicate class CartCombinator(val cartField: CartField) { def If (cond: CartPredicate): CartCombinator = new CartCombinator((c: Cart) => if (cond(c)) cartField(c) else 0) def > (value: BigDecimal): CartPredicate = ((c: Cart) => cartField(c) > value) . . .
  • 57. adding a predicate class CartCombinator(val cartField: CartField) { def and (comb: CartCombinator): CartCombinator = new CartCombinator((c: Cart) => comb.cartField(c) + cartField(c)) def & (occ: CartCombinator): CartCombinator = and(occ) def If (cond: CartPredicate): CartCombinator = new CartCombinator((c: Cart) => if (cond(c)) cartField(c) else 0) def > (value: BigDecimal): CartPredicate = ((c: Cart) => cartField(c) > value) }
  • 58. “for Finland the tax is 5%, when calculating the tax ignore discount if shipping costs larger than 4 ” For(FINLAND).take(0.05).ignore (discount.If(shipping > 4)) For(FINLAND) take 0.05 ignore (discount If shipping > 4)
  • 59. What's going on under the hood ? For(FINLAND) take 0.05 ignore (discount If shipping > 4) (invokes) (returns) CountryToTaxCalculation CartCombinator (returns) CountryContainer (invokes)(returns) TaxAndCountry container (returns)
  • 60. Adding custom values case class TaxAndCountry(country: Country, tax: BigDecimal) { def addTouristPrice (f: Cart => BigDecimal): CountryToTaxCalculation = addCustomValue(f) private def addCustomValue (f:CartField): CountryToTaxCalculation = (country, (cart: Cart) => (cart.total + f(cart)) * tax) def ignore(fs: CartCombinator*): CountryToTaxCalculation = { . . .
  • 61. What's going on under the hood ? For(Israel) take 0.25 addTouristPrice { cart => cart.total * 0.2} CountryContainer (invokes)(returns) (returns) TaxAndCountry container (invokes) (returns) CountryToTaxCalculation
  • 62. Tax Rules DSL val taxRules: Seq[CountryToTax] = Seq( For(CANADA) take 0.15 , For(UK) take 0.12 ignore shipping, For(USA) take 0.07 ignore discount & shipping, For(FINLAND) take 0.05 ignore (discount If shipping > 4), For(Israel) take 0.25 addTouristPrice{ cart => cart.total * 0.2 } )
  • 63. English DSL ● “for Canada the tax is 15%" For(CANADA) take 0.15 ● “for UK the tax is 12%, when calculating the tax ignore the shipping cost” For(UK) take 0.12 ignore shipping ● “for the USA the tax is 7%, ignore the shipping cost and the discount" For(USA) take 0.07 ignore discount & shipping ● “for Finland the tax is 5% ignore discount if shipping costs larger than 4” For(FIN) take 0.05 ignore (discount If shipping > 4) ● “for Israel the tax is 25%, add a special custom tourist tax just for fun” For(Israel) take 0.25 addTouristPrice {cart => cart.total * 0.2}
  • 64. Matching tax rule for a cart import TaxDsl._ val taxRules: Seq[CountryToTax] val cart = Cart(total = 100, shipping = 10,discount = 20, country = USA) val taxRule = taxRules findRuleFor cart To activate this we need to enrich an existing class
  • 65. Enriching Seq implicit class TaxRuleSeq(taxRules: Seq[CountryToTaxCalculation]) { def findRuleFor(cart: Cart) = { taxRules.find(_._1 == cart.country).map(_._2) } }
  • 66. Errors and exceptions ● Always use the domain language to express any exception that might occur during processing ● The compiler acts as the policeman for you
  • 67. Final notes ● getting the syntax right is hard ● A DSL needs only to be expressive enough for the user ● DSLs are fun !
  • 68. Q&A This is where you are going to present your final words. This slide is not meant to have a lot of text.Thank You! Any Questions? Alon Muchnick @WixEngalonmu@wix.com

Editor's Notes

  1. implicit are methods and fields that are wired implicitly by the scala compiler by searching for an matching signature in the current scope. Scala offers the power of open classes through its implicit construct, as we discussed in the Scala implicits sidebar in section 3.2. Difference with Ruby monkey patching: Scala implicits are lexically scoped; the added behavior via implicit conversions needs to be explicitly imported into specific lexical scopes (see [2] in section 6.10)
  2. using an implicit