SlideShare a Scribd company logo
1 of 68
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

Jetpack Compose - A Lightning Tour
Jetpack Compose - A Lightning TourJetpack Compose - A Lightning Tour
Jetpack Compose - A Lightning TourMatthew Clarke
 
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 ErlandsonDatabricks
 
The Kokkos C++ Performance Portability EcoSystem
The Kokkos C++ Performance Portability EcoSystemThe Kokkos C++ Performance Portability EcoSystem
The Kokkos C++ Performance Portability EcoSysteminside-BigData.com
 
Billing in a supermarket c++
Billing in a supermarket c++Billing in a supermarket c++
Billing in a supermarket c++varun arora
 
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.pptxVitsRangannavar
 
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 youSmart Chicago Collaborative
 
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.glAll Things Open
 
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.docxdirkrplav
 
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 AssistantOrestes 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

Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationkaushalgiri8080
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfkalichargn70th171
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantAxelRicardoTrocheRiq
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)OPEN KNOWLEDGE GmbH
 
cybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningcybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningVitsRangannavar
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - InfographicHr365.us smith
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 

Recently uploaded (20)

Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanation
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service Consultant
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)
 
cybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningcybersecurity notes for mca students for learning
cybersecurity notes for mca students for learning
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - Infographic
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 

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