SlideShare une entreprise Scribd logo
1  sur  134
Télécharger pour lire hors ligne
Type-safe Embedded
Domain-Specific Languages
Arthur Xavier
@arthurxavierx
1. Domain-specific languages
Domain modelling and DSLs
What makes a (good) DSL?
Examples
2. Language Oriented Programming
Playing with a real-world DSL
Discuss the techniques used
Language Oriented Programming
3. Type-safe embedding
Embedding techniques
Type-safety for DSLs
A DSL for validating business rules
4. Da Capo al Coda
A DSL for chatbots with indexed monads
Modifying the chatbot DSL
Wrap-up and conclusion
1Domain-specific languages
Domains come in all shapes and colors
Testing
Validation
Financial services
Storage
Database querying
GUI development
Voice controllers
…
Infotaiment systems
Mobile app development
Web forms
Data visualization
Text documents
3D graphics
Architectural modelling
…
And it’s our job to translate all of this into code
Language is the essence of abstraction
1 + 2 + 3 + 4
1 + 2 + 3 + ...
1 + 2 + 3 + 4
10
3 + 3 + 4
1 + (2 + (3 + 4))
□
∞
10
6
((1 + 2) + 3) + ...
□
1 + 2 + 3 + 4
1 + 2 + 3 + ...
Language ≝ syntax + semantics
DSL ≝ domain + language
DSL ≝ model + language
DSL ≝ model + syntax + semantics
data Syntax = ...
semantics :: Syntax -> _
DSL ≝ model + syntax + semantics
data Expr -- Abstract Syntax Tree
= Val Bool
| And Expr Expr
| Or Expr Expr
| Bla Expr
eval :: Expr -> Bool
eval (Val x) = x
eval (And a b) = eval a && eval b
eval (Or a b) = eval a || eval b
eval (Bla a) = not (eval a)
DSL ≝ model + syntax + semantics
DSL ≝ model + syntax + semantics
print :: Expr -> String
print (Val x) = show x
print (And a b) = "(" ++ print a ++ " ∧ " ++ print b ++ ")"
print (Or a b) = "(" ++ print a ++ " ∨ " ++ print b ++ ")"
print (Bla a) = "¬" ++ print a
data Expr
= Val Bool
| And Expr Expr
| Or Expr Expr
| Bla Expr
DSL ≝ primitives + composition + interpretation
data Primitives = ...
combinator :: _ -> Primitives -> Primitives
interpreter :: Primitives -> _
DSL ≝ primitives + composition + interpretation
DSL ≝ primitives + composition + interpretation
data Contract
= Transfer Person Person Money DateTime
| Sell Person Person Product DateTime
| Sequence Contract Contract
| Freeze Contract
| Cancel Contract
| ...
calculate :: Contract -> Money
perform :: Contract -> IO ()
simulate :: Contract -> World
validate :: Contract -> Maybe ContractError
syntax and semantics
The goal is to encode domain rules in both
syntax and semantics
The goal is to encode invariants in both
The human factor
Syntax plays a big role
(it’s what us humans manipulate)
Correctness by construction is important
A good DSL
Simple
Concise
Conforming
Composable
Correct
A good DSL
Expresses problems using a specific vocabulary
Gives us simple, composable words
Lets us build up larger and correct systems
“in our own words”
Why?
To make things simple
To make things pretty
To make things fast
To make things correct
Any combination of the above
Examples
External DSLs
HTML
CSS
SQL
LaTeX
Makefile
VimL
Elm
Dhall
Solidity
Parsec
HSpec
Persistent’s Entity Syntax
Esqueleto
Servant routes
Examples
Embedded DSLs in Haskell
recipe :: Parser Recipe
recipe = do
rn <- lexeme stringLike
lexeme (syntacticSugar "is made with") *> string "rn"
i <- many1 ingredient
many1 (string "rn")
lexeme (string "prepared by") *> string "rn"
s <- many1 step
return $ Recipe rn i s
https://www.schoolofhaskell.com/user/adinapoli/the-pragmatic-haskeller/episode-5-a-simple-dsl
mySpec :: Spec
mySpec = do
describe "Prelude.head" $ do
it "returns the first element of a list" $ do
head [23 ..] `shouldBe` (23 :: Int)
it "returns the first element of an *arbitrary* list" $ do
property $ x xs ->
head (x:xs) == (x :: Int)
it "throws an exception if used with an empty list" $ do
evaluate (head []) `shouldThrow` anyException
https://hspec.github.io/
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist|
Person
name String
age Int Maybe
deriving Eq Show
BlogPost
title String
authorId PersonId
deriving Eq Show
Follow
follower PersonId
followed PersonId
deriving Eq Show
|] https://www.yesodweb.com/book/persistent
recentArticles :: SqlPersistT m [(Entity User, Entity Article)]
recentArticles =
select . from $ (users `InnerJoin` articles) -> do
on (users ^.UserId ==. articles ^.ArticleAuthorId)
orderBy [desc (articles ^.ArticlePublishedTime)]
limit 10
return (users, articles)
https://mmhaskell.com/web-skills-5
type UserAPI
= "users"
:> ReqBody '[JSON] User
:> Post '[JSON] User
:<|>
"users"
:> Capture "userId" Integer
:> ReqBody '[JSON] User
:> Put '[JSON] User
https://docs.servant.dev/en/stable/tutorial/ApiType.html
type UserAPI
= "users"
( ReqBody '[JSON] User
:> Post '[JSON] User
:<|>
Capture "userId" Integer
:> ReqBody '[JSON] User
:> Put '[JSON] User
)
https://docs.servant.dev/en/stable/tutorial/ApiType.html
“If you have a set of things and a means of combining them,
then it’s a language.”
https://parametri.city/blog/2018-12-23-language-oriented-software-engineering
Questions?
2Language oriented programming
A DSL for forms
https://github.com/lumihq/purescript-lumi-components
https://lumihq.github.io/purescript-lumi-components/#/form
$ git clone https://github.com/arthurxavierx/monadic-party-edsl.git
$ cd monadic-party-edsl/forms
$ make watch
newtype Registration = Registration
{ email :: EmailAddress
, password :: NonEmptyString
}
registrationForm :: FormBuilder _ RegistrationFormData Registration
registrationForm = ado
email <-
indent "Email" Required
$ focus _email
$ validated (isValidEmail "Email")
$ validated (nonEmpty "Email")
$ textbox
password <-
indent "Password" Required
$ focus _password
$ validated (nonEmpty "Password")
$ passwordBox
in
Registration
{ email
, password
}
type RegistrationFormData =
{ email :: Validated String
, password :: Validated String
}
_email :: forall a r. Lens' { email :: a | r } a
_email = lens _.email _{ email = _ }
_password :: forall a r. Lens' { password :: a | r } a
_password = lens _.password _{ password = _ }
isValidEmail :: Validator String EmailAddress
-- isValidEmail :: String -> Either String EmailAddress
registrationForm :: FormBuilder _ RegistrationFormData Registration
registrationForm = ado
email <-
indent "Email" Required
$ focus (lens _.email _{ email = _ })
$ validated (isValidEmail "Email")
$ validated (nonEmpty "Email")
$ textbox
password <-
indent "Password" Required
$ focus (lens _.password _{ password = _ })
$ validated (nonEmpty "Password")
$ passwordBox
in
Registration
{ email
, password
}
email <-
indent "Email" Required
$ focus (prop (SProxy :: _ "email"))
$ validated (isValidEmail "Email")
$ textbox
password <-
indent "Password" Required
$ focus (prop (SProxy :: _ "password"))
$ validated (nonEmpty "Password")
$ passwordBox
_ <- withValue { password } ->
indent "Password confirmation" Required
$ focus _passwordConfirmation
$ validated (pc ->
if pc == fromValidated password then
Right pc
else
Left "Passwords do not match."
)
$ passwordBox
in
Registration
{ email
, password
}
How does it work?
newtype FormBuilder value result = FormBuilder
( value
-> { edit :: ((value -> value) -> Effect Unit) -> UI
, validate :: Maybe result
}
)
instance Applicative (FormBuilder props value)
newtype FormBuilder props value result = FormBuilder
( props
-> value
-> { edit :: ((value -> value) -> Effect Unit) -> UI
, validate :: Maybe result
}
)
instance Applicative (FormBuilder props value)
type FormUI value = ((value -> value) -> Effect Unit) -> UI
newtype FormBuilder props value result =
FormBuilder
(ReaderT (Tuple props value) (WriterT FormUI Maybe) result)
instance Applicative (FormBuilder props value)
textbox :: forall props. FormBuilder props String String
passwordBox :: forall props. FormBuilder props String String
switch :: forall props. FormBuilder props Boolean Boolean
indent
:: forall props value result
. String
-> RequiredField
-> FormBuilder props value result
-> FormBuilder props value result
focus
:: forall props s a result
. Lens' s a
-> FormBuilder props a result
-> FormBuilder props s result
validated
:: forall props value result_ result
. (result_ -> Either String result)
-> FormBuilder props value result_
-> FormBuilder props (Validated value) result
withValue
:: forall props value result
. (value -> FormBuilder props value result)
-> FormBuilder props value result
build
:: forall props value result
. FormBuilder props value result
-> { value :: value
, onChange :: (value -> value) -> Effect Unit
| props
}
-> JSX
revalidate
:: forall props value result
. FormBuilder props value result
-> props
-> value
-> Maybe result
Questions?
Multiple DSLs for building complex forms
newtype Wizard props value result =
Wizard
(Free (FormBuilder props value) result)
derive newtype instance Monad (Wizard props value)
step
:: forall props value result
. FormBuilder props value result
-> Wizard props value result
newtype TableFormBuilder props value result = ...
instance Applicative (TableFormBuilder props value)
column
:: forall props row result
. String
-> FormBuilder props row result
-> TableFormBuilder props row result
table
:: forall props row result
. TableFormBuilder props row result
-> FormBuilder props (Array row) (Array result)
Language oriented programming
interpreterA :: LanguageA -> LanguageB
interpreterB :: LanguageB -> LanguageC
interpreterC :: LanguageC -> LanguageD
···
Language oriented programming
Design a domain-specific language for the core
application logic
Write the application in the DSL
Build interpreters to execute the DSL programs
Language oriented programming
Abstracting business problems as programming
language problems, so that solutions are DSLs
How to abstract things ⇔ how to split things up
and join them back together
Language building ≅ domain modelling
https://parametri.city/blog/2018-12-23-language-oriented-software-engineering
“[…] you cannot know what the DSL will be ahead of time, you
have to evolve it alongside the concrete implementation.”
Questions?
3Type-safe embedding
data Syntax = ...
semantics :: Syntax -> _
DSL ≝ model + syntax + semantics
data Primitives = ...
combinator :: _ -> Primitives -> Primitives
interpreter :: Primitives -> _
DSL ≝ primitives + composition + interpretation
Feasting on the host language
Expressions
Control flow
Data types
Effects
Recursion, unfortunately?
Shallow × deep embedding
Shallow × deep embedding
Who does the work, interpreters or constructors?
Extending: new interpretations or new constructors?
Deep is simple, but shallow is direct
newtype FormBuilder props value result = FormBuilder
( props
-> value
-> { edit :: ((value -> value) -> Effect Unit) -> UI
, validate :: Maybe result
}
)
Shallow embedding
Shallow embedding
Syntax is defined in terms of the semantic domain
More flexibility for adding new combinators
(under a specific interpretation)
Adding a new interpreter might imply:
● adding some new set of constructors
● and/or refactoring the semantic domain to include the new
interpretation, then rework all constructors
data FormBuilder props value result where
Textbox :: FormBuilder props String String
Password :: FormBuilder props String String
···
Focus
:: Lens' s a
-> FormBuilder props a result
-> FormBuilder props s result
···
Deep embedding
More flexibility for adding new interpreters
(for a specific set of constructors)
Adding new constructors might imply reworking
all interpreters
Deep embedding
“[…] The goal is to define a datatype by cases, where one can
add new cases to the datatype and new functions over the
datatype, without recompiling existing code, and while
retaining static type safety.”
https://en.wikipedia.org/wiki/Expression_problemExpression problem
Monoids
class Semigroup a where
(<>) :: a -> a -> a
class Monoid a where
mempty :: a
data Document = Empty | Block [Block] | Inline [Inline]
data Block = Button String | Header String | Paragraph Inline | ...
data Inline = Text String | Strong String | Link URL String | ...
instance Semigroup Document where
Empty <> Empty = Empty
Empty <> Block b = b
Block b <> Empty = b
···
Block (Button l) <> Inline (Text s) = Block (Button (l <> s))
···
instance Monoid Document where
mempty = Empty
myDocument :: Document
myDocument =
fold
[ Block
[ Header "Hello, world!"
, Paragraph "Lorem ipsum dolor sit amet, consectetur ..."
, Button "Click me"
]
, Block
[ Paragraph "Look! An image!"
, Image "https://whatever.com/whatever.jpg"
]
]
{-# LANGUAGE RebindableSyntax #-}
myDocument :: Document
myDocument = do
Block
[ Header "Hello, world!"
, Paragraph "Lorem ipsum dolor sit amet, consectetur ..."
, Button "Click me"
]
Block
[ Paragraph "Look! An image!"
, Image "https://whatever.com/whatever.jpg"
]
where
(>>) = (<>)
Applicative functors
class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
-- :: f a -> f b -> f (a, b)
data AppConfig = AppConfig
{ hostname :: String
, port :: Int
, emailKey :: String
, emailPassword :: String
}
appConfig :: EnvConfig AppConfig
appConfig =
AppConfig
<$> string "hostname"
<*> int "port"
<*> string "emailKey"
<*> string "emailPassword"
{#- LANGUAGE ApplicativeDo #-}
appConfig :: EnvConfig AppConfig
appConfig = do
hostname <- string "hostname"
port <- int "port"
emailKey <- string "emailKey"
emailPassword <- string "emailPassword"
pure $
AppConfig
{ hostname = hostname
, port = port
, emailKey = emailKey
, emailPassword = emailPassword
}
Monads
class Applicative m => Monad m where
bind :: m a -> (a -> m b) -> m b
data AppConfig = AppConfig
{ hostname :: String
, port :: Int
, emailKey :: String
, emailPassword :: String
, emailDefaultFrom :: Maybe String
}
appConfig :: EnvConfig AppConfig
appConfig = do
hostname <- string "hostname"
port <- int "port"
emailKey <- string "emailKey"
emailPassword <- string "emailPassword"
emailDefaultFrom <-
if isJust emailKey then
Just <$> string "emailDefaultFrom"
else
pure Nothing
pure $
AppConfig
{ hostname = hostname
, port = port
, emailKey = emailKey
, emailPassword = emailPassword
, emailDefaultFrom = emailDefaultFrom
}
“[…] The goal is to define a datatype by cases, where one can
add new cases to the datatype and new functions over the
datatype, without recompiling existing code, and while
retaining static type safety.”
https://en.wikipedia.org/wiki/Expression_problemExpression problem
Tagless final
Tagless final
Typeclasses as syntax
Instances as semantics
class Monoid d => MathDoc d where
text :: String -> d
(-) :: d -> d -> d -- subscripting
(^) :: d -> d -> d -- superscripting
(/) :: d -> d -> d -- fraction
http://okmij.org/ftp/tagless-final/MathLayout.hs
data LaTeX = LaTeX String
instance Monoid LaTeX ...
instance MathDoc LaTeX where
text = LaTeX
LaTeX a - LaTeX b = LaTeX (a ++ "_{" ++ b ++ "}")
LaTeX a ^ LaTeX b = LaTeX (a ++ "^{" ++ b ++ "}")
LaTeX a / LaTeX b = LaTeX ("frac{" ++ a ++ "}{" ++ b ++ "}")
http://okmij.org/ftp/tagless-final/MathLayout.hs
Tagless final
Vertical extensibility: adding new interpreters
Horizontal extensibility: adding new terms
data LaTeX = LaTeX String
instance MathDoc LaTeX where
text = LaTeX
LaTeX a - LaTeX b = LaTeX (a ++ "_{" ++ b ++ "}")
LaTeX a ^ LaTeX b = LaTeX (a ++ "^{" ++ b ++ "}")
LaTeX a / LaTeX b = LaTeX ("frac{" ++ a ++ "}{" ++ b ++ "}")
instance MathDoc String where
text = id
a - b = a ++ "_" ++ b
a ^ b = a ++ "^" ++ b
a / b = a ++ "/" ++ b
http://okmij.org/ftp/tagless-final/MathLayout.hs
class MathDoc d where
text :: String -> d
(-) :: d -> d -> d -- subscripting
(^) :: d -> d -> d -- superscripting
(/) :: d -> d -> d -- fraction
data GreekLetter d = GreekLetter Char
alpha = greek $ GreekLetter 'ɑ'
class MathDoc d => Greek d where
greek :: GreekLetter -> d
class MathDoc d => Circle d where
circle :: d
http://okmij.org/ftp/tagless-final/MathLayout.hs
http://okmij.org/ftp/tagless-final/MathLayout.hs
-- doc1 :: (Circle d, Greek d) => d
doc1 = alpha - (text "1") ^ circle
doc1_string :: String
doc1_string = doc1
-- > "ɑ_1^º"
doc1_latex :: LaTeX
doc1_latex = doc1
-- > LaTeX "alpha_1^circ"
Tagless final
Composing constraints ≅ defining capabilities
MTL uses tagless final for expressing effects
Type classes can be problematic sometimes
ExampleBusiness rules and validation
Goals
● Validate events in an environment
○ Purchase, registration, product viewing, etc.
● Perform operations after events
○ Sending emails, updating the database, etc.
data Registration = Registration
{ firstName :: Text
, lastName :: Text
, email :: EmailAddress
, password :: Password
, country :: Country
}
data User = ...
data BillingInfo = ...
data Purchase = ...
data Product = ...
data Env = Env
{ envUser :: Maybe User
···
}
businessRules =
fold
[ to buyProduct validatePurchase
, to register validateRegistration
, to viewProduct validateViewProduct
, ···
, to buyProduct validateUserBilling
, after buyProduct sendPurchaseEmail
, ···
]
where
validatePurchase :: Purchase -> _
···
{-# LANGUAGE RebindableSyntax #-}
businessRules = do
to buyProduct validatePurchase
to register validateRegistration
to viewProduct validateViewProduct
···
to buyProduct validateUserBilling
after buyProduct sendPurchaseEmail
···
where
(>>) = (<>)
validatePurchase :: Purchase -> _
···
validatePurchase purchase = do
user <- authenticate
is
(available
(orderedQuantity `of_` purchase)
(orderedProduct `of_` purchase))
exists (price `of_` orderedProduct `of_` purchase)
exists (country `of_` user
done
where
available q product = quantity `of_` product >= q
validateUserBilling _ = do
user <- authenticate
exists (billingInfo `of_` user)
done
authenticate = do
env <- getEnv
exists (envUser env)
What is a possible solution?
Let’s first think about the constraints
1. Validation rules depend on an environment.
2. Validation rules are sequentially composable.
3. Business rules can be combined.
4. The input to a validation rule or to an effect after
an event must match the event’s contents.
5. Dispatching an event to a set of business rules
must give us back (maybe) an effect.
data Event
= BuyProduct Purchase
| Register Registration
| ViewProduct Product
type Match f a = f -> Maybe a
to :: Match event e -> (e -> Rules) -> Rules
after :: Match event e -> (env -> e -> m a) -> Rules
dispatch :: Applicative m => Rules -> env -> event -> Maybe (m ())
is :: Bool -> Validation ()
exists :: Maybe a -> Validation a
getEnv :: Validation env
runValidation :: Validation a -> env -> Maybe a
-- Syntax sugar
infixr 9 `of_`
of_ :: (a -> b) -> a -> b
of_ = ($)
done :: Monad m => m ()
done = return ()
What if we wanted to depend on a database to
perform validations?
What if we wanted to add proper error messages?
Try expressing rules with both deep and shallow
embedding.
What if we wanted to have a pipeline instead?
That is, feeding validated outputs to after rules.
Tip: one possible solution requires having only a single type of
rule instead of the original two: to and after.
Exercises
Can we refactor Validation to be written in terms
of classic monad transformers?
How could we statically render validation rules as
documentation text?
How can we restrict the type of effects that can be
performed after each event?
Tip: you can either use records of effects or ConstraintKinds.
Food for thought
Questions?
4Da Capo al Coda
ExampleChatbots with indexed monads
$ git clone https://github.com/arthurxavierx/monadic-party-edsl.git
$ cd monadic-party-edsl/chatbot
$ npm ci
$ make run
FinalizedStart Answer Ask
ε start
answer
ask
finalize
class IxFunctor f where
imap :: (a -> b) -> f x y a -> f x y b
class IxFunctor m => IxApplicative m where
iapply :: m x y (a -> b) -> m y z a -> m x z b
:: m x y a -> m y z b -> m x z (a, b)
ipure :: a -> m x x a
class IxApplicative m => IxMonad m where
ibind :: m x y a -> (a -> m y z b) -> m x z b
preferredLanguage = Ix.do
start any
answer "Hey there!"
language <- askPreferences
loop assertPreferences language
answer "Nice!"
finalize
preferredLanguage = Ix.do
start any
answer "Which one do you prefer, Haskell or PureScript?"
language <- ask parseLanguage
loop language lang -> Ix.do
if lang == PureScript then
break unit
else Ix.do
answer "Errrr... Which one do you really prefer?"
lang' <- ask parseLanguage
continue lang'
answer "Nice!"
finalize
where
parseLanguage = do
prefers <-
(Haskell <$ match "haskell") <|>
(PureScript <$ match "purescript")
pure { prefers }
Questions?
Exercises
How can we allow multiple consecutive answers and
questions while keeping the initial and final transitions?
How could we make the DSL strictly applicative?
And what implications does this change effectively have?
Tip: we’ll need to add two new combinators.
Food for thought
Can you think of a simpler way to express the same DSL?
How would you improve the domain modelling?
How would your changes affect the DSL?
?Wrapping up
Language is the essence of abstraction.
A language can be seen as a pair syntax + semantics.
We can write tiny languages embedded in Haskell
that correctly model a domain.
Wrapping up
We can make use of fundamental typeclasses such as
Monoid, Applicative and Monad to embed a
language more ergonomically and safely.
We can define the syntax of a DSL in Haskell in terms
of its abstract syntax or of its semantic domain.
(deep × shallow embedding)
Wrapping up
Wrapping up
We can use typeclasses and instances to embed
extendable DSLs in Haskell. (tagless final style)
We can leverage Haskell’s powerful type system to
make our DSLs more expressive and safe.
Creativity is key, but respecting laws is important.
Separation of concerns
High development productivity
Highly maintainable design
Less lines of less complex code
⇒ more maintainable code.
Highly portable design
Depending on the choices regarding
embedding techniques.
The good
Opportunities for reuse
User enhanceable systems
Fewer bugs
Improved adaptability
The good
The bad
A hard design problem
Up-front cost
A tendency do use multiple languages
Language cacophony
Maintenance can sometimes be painful
The ugly
Monad transformers
Free applicatives & free monads
Foldable, Traversable, Alternative
Type-level programming
Functor combinators: Sum, Product, Compose,
Day, Yoneda, Coyoneda, Alt
Abstract interpretation
Further reading
Share your thoughts and conclusions!
References
Ward, M. P. (1994). Language Oriented Programming.
Van Deursen, A. et al. (2000). Domain-specific languages: An annotated bibliography.
Kiselyov, O. (2012). Typed Tagless Final Interpreters.
Gibbons, J., & Wu, N. (2014). Folding Domain-Specific Languages: Deep and Shallow
Embeddings.

Contenu connexe

Tendances

11. session 11 functions and objects
11. session 11   functions and objects11. session 11   functions and objects
11. session 11 functions and objects
Phúc Đỗ
 
Working With JQuery Part1
Working With JQuery Part1Working With JQuery Part1
Working With JQuery Part1
saydin_soft
 
Ruby Language - A quick tour
Ruby Language - A quick tourRuby Language - A quick tour
Ruby Language - A quick tour
aztack
 

Tendances (20)

Max Koretskyi "Why are Angular and React so fast?"
Max Koretskyi "Why are Angular and React so fast?"Max Koretskyi "Why are Angular and React so fast?"
Max Koretskyi "Why are Angular and React so fast?"
 
Model-Driven Software Development - Pretty-Printing, Editor Services, Term Re...
Model-Driven Software Development - Pretty-Printing, Editor Services, Term Re...Model-Driven Software Development - Pretty-Printing, Editor Services, Term Re...
Model-Driven Software Development - Pretty-Printing, Editor Services, Term Re...
 
DIWE - Working with MySQL Databases
DIWE - Working with MySQL DatabasesDIWE - Working with MySQL Databases
DIWE - Working with MySQL Databases
 
11. session 11 functions and objects
11. session 11   functions and objects11. session 11   functions and objects
11. session 11 functions and objects
 
Headless Js Testing
Headless Js TestingHeadless Js Testing
Headless Js Testing
 
Indexing thousands of writes per second with redis
Indexing thousands of writes per second with redisIndexing thousands of writes per second with redis
Indexing thousands of writes per second with redis
 
Java script
Java scriptJava script
Java script
 
Bottom Up
Bottom UpBottom Up
Bottom Up
 
Javascript essentials
Javascript essentialsJavascript essentials
Javascript essentials
 
Object Calisthenics em Go
Object Calisthenics em GoObject Calisthenics em Go
Object Calisthenics em Go
 
Working With JQuery Part1
Working With JQuery Part1Working With JQuery Part1
Working With JQuery Part1
 
Types and Immutability: why you should care
Types and Immutability: why you should careTypes and Immutability: why you should care
Types and Immutability: why you should care
 
AngularJS
AngularJSAngularJS
AngularJS
 
Ruby Language - A quick tour
Ruby Language - A quick tourRuby Language - A quick tour
Ruby Language - A quick tour
 
Django 1.1 Tour
Django 1.1 TourDjango 1.1 Tour
Django 1.1 Tour
 
Python: Basic Inheritance
Python: Basic InheritancePython: Basic Inheritance
Python: Basic Inheritance
 
Functional DDD
Functional DDDFunctional DDD
Functional DDD
 
Functional Programming with Groovy
Functional Programming with GroovyFunctional Programming with Groovy
Functional Programming with Groovy
 
Solid in practice
Solid in practiceSolid in practice
Solid in practice
 
Pragmatic Real-World Scala (short version)
Pragmatic Real-World Scala (short version)Pragmatic Real-World Scala (short version)
Pragmatic Real-World Scala (short version)
 

Similaire à Type safe embedded domain-specific languages

The MongoDB Driver for F#
The MongoDB Driver for F#The MongoDB Driver for F#
The MongoDB Driver for F#
MongoDB
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
Dmitry Buzdin
 
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
MongoSF
 
FleetDB: A Schema-Free Database in Clojure
FleetDB: A Schema-Free Database in ClojureFleetDB: A Schema-Free Database in Clojure
FleetDB: A Schema-Free Database in Clojure
Mark McGranaghan
 
FleetDB A Schema-Free Database in Clojure
FleetDB A Schema-Free Database in ClojureFleetDB A Schema-Free Database in Clojure
FleetDB A Schema-Free Database in Clojure
elliando dias
 

Similaire à Type safe embedded domain-specific languages (20)

Creating Domain Specific Languages in Python
Creating Domain Specific Languages in PythonCreating Domain Specific Languages in Python
Creating Domain Specific Languages in Python
 
The MongoDB Driver for F#
The MongoDB Driver for F#The MongoDB Driver for F#
The MongoDB Driver for F#
 
Refactoring to Macros with Clojure
Refactoring to Macros with ClojureRefactoring to Macros with Clojure
Refactoring to Macros with Clojure
 
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
Map/reduce, geospatial indexing, and other cool features (Kristina Chodorow)
 
Elm: give it a try
Elm: give it a tryElm: give it a try
Elm: give it a try
 
JavaScript Objects and OOP Programming with JavaScript
JavaScript Objects and OOP Programming with JavaScriptJavaScript Objects and OOP Programming with JavaScript
JavaScript Objects and OOP Programming with JavaScript
 
DataMapper
DataMapperDataMapper
DataMapper
 
J Query Public
J Query PublicJ Query Public
J Query Public
 
A Tour of Building Web Applications with R Shiny
A Tour of Building Web Applications with R Shiny A Tour of Building Web Applications with R Shiny
A Tour of Building Web Applications with R Shiny
 
FleetDB: A Schema-Free Database in Clojure
FleetDB: A Schema-Free Database in ClojureFleetDB: A Schema-Free Database in Clojure
FleetDB: A Schema-Free Database in Clojure
 
FleetDB A Schema-Free Database in Clojure
FleetDB A Schema-Free Database in ClojureFleetDB A Schema-Free Database in Clojure
FleetDB A Schema-Free Database in Clojure
 
Imugi: Compiler made with Python
Imugi: Compiler made with PythonImugi: Compiler made with Python
Imugi: Compiler made with Python
 
Unfiltered Unveiled
Unfiltered UnveiledUnfiltered Unveiled
Unfiltered Unveiled
 
PureScript & Pux
PureScript & PuxPureScript & Pux
PureScript & Pux
 
Building DSLs with the Spoofax Language Workbench
Building DSLs with the Spoofax Language WorkbenchBuilding DSLs with the Spoofax Language Workbench
Building DSLs with the Spoofax Language Workbench
 
Writing a compiler in go
Writing a compiler in goWriting a compiler in go
Writing a compiler in go
 
ScalikeJDBC Tutorial for Beginners
ScalikeJDBC Tutorial for BeginnersScalikeJDBC Tutorial for Beginners
ScalikeJDBC Tutorial for Beginners
 
CS101- Introduction to Computing- Lecture 29
CS101- Introduction to Computing- Lecture 29CS101- Introduction to Computing- Lecture 29
CS101- Introduction to Computing- Lecture 29
 
MongoDB Aggregation Framework
MongoDB Aggregation FrameworkMongoDB Aggregation Framework
MongoDB Aggregation Framework
 
DIWE - Advanced PHP Concepts
DIWE - Advanced PHP ConceptsDIWE - Advanced PHP Concepts
DIWE - Advanced PHP Concepts
 

Dernier

CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
masabamasaba
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
Health
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
VictoriaMetrics
 

Dernier (20)

WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK Software
 
%in Harare+277-882-255-28 abortion pills for sale in Harare
%in Harare+277-882-255-28 abortion pills for sale in Harare%in Harare+277-882-255-28 abortion pills for sale in Harare
%in Harare+277-882-255-28 abortion pills for sale in Harare
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
Harnessing ChatGPT - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT  - Elevating Productivity in Today's Agile EnvironmentHarnessing ChatGPT  - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT - Elevating Productivity in Today's Agile Environment
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 

Type safe embedded domain-specific languages

  • 2. 1. Domain-specific languages Domain modelling and DSLs What makes a (good) DSL? Examples 2. Language Oriented Programming Playing with a real-world DSL Discuss the techniques used Language Oriented Programming 3. Type-safe embedding Embedding techniques Type-safety for DSLs A DSL for validating business rules 4. Da Capo al Coda A DSL for chatbots with indexed monads Modifying the chatbot DSL Wrap-up and conclusion
  • 4. Domains come in all shapes and colors
  • 5. Testing Validation Financial services Storage Database querying GUI development Voice controllers … Infotaiment systems Mobile app development Web forms Data visualization Text documents 3D graphics Architectural modelling …
  • 6. And it’s our job to translate all of this into code
  • 7. Language is the essence of abstraction
  • 8. 1 + 2 + 3 + 4 1 + 2 + 3 + ...
  • 9. 1 + 2 + 3 + 4 10 3 + 3 + 4 1 + (2 + (3 + 4)) □ ∞ 10 6 ((1 + 2) + 3) + ... □ 1 + 2 + 3 + 4 1 + 2 + 3 + ...
  • 10.
  • 11.
  • 12. Language ≝ syntax + semantics
  • 13. DSL ≝ domain + language
  • 14. DSL ≝ model + language
  • 15. DSL ≝ model + syntax + semantics
  • 16. data Syntax = ... semantics :: Syntax -> _ DSL ≝ model + syntax + semantics
  • 17. data Expr -- Abstract Syntax Tree = Val Bool | And Expr Expr | Or Expr Expr | Bla Expr eval :: Expr -> Bool eval (Val x) = x eval (And a b) = eval a && eval b eval (Or a b) = eval a || eval b eval (Bla a) = not (eval a) DSL ≝ model + syntax + semantics
  • 18. DSL ≝ model + syntax + semantics print :: Expr -> String print (Val x) = show x print (And a b) = "(" ++ print a ++ " ∧ " ++ print b ++ ")" print (Or a b) = "(" ++ print a ++ " ∨ " ++ print b ++ ")" print (Bla a) = "¬" ++ print a data Expr = Val Bool | And Expr Expr | Or Expr Expr | Bla Expr
  • 19. DSL ≝ primitives + composition + interpretation
  • 20. data Primitives = ... combinator :: _ -> Primitives -> Primitives interpreter :: Primitives -> _ DSL ≝ primitives + composition + interpretation
  • 21. DSL ≝ primitives + composition + interpretation data Contract = Transfer Person Person Money DateTime | Sell Person Person Product DateTime | Sequence Contract Contract | Freeze Contract | Cancel Contract | ... calculate :: Contract -> Money perform :: Contract -> IO () simulate :: Contract -> World validate :: Contract -> Maybe ContractError
  • 22. syntax and semantics The goal is to encode domain rules in both
  • 23. syntax and semantics The goal is to encode invariants in both
  • 24. The human factor Syntax plays a big role (it’s what us humans manipulate) Correctness by construction is important
  • 26. A good DSL Expresses problems using a specific vocabulary Gives us simple, composable words Lets us build up larger and correct systems “in our own words”
  • 27. Why? To make things simple To make things pretty To make things fast To make things correct Any combination of the above
  • 29. Parsec HSpec Persistent’s Entity Syntax Esqueleto Servant routes Examples Embedded DSLs in Haskell
  • 30. recipe :: Parser Recipe recipe = do rn <- lexeme stringLike lexeme (syntacticSugar "is made with") *> string "rn" i <- many1 ingredient many1 (string "rn") lexeme (string "prepared by") *> string "rn" s <- many1 step return $ Recipe rn i s https://www.schoolofhaskell.com/user/adinapoli/the-pragmatic-haskeller/episode-5-a-simple-dsl
  • 31. mySpec :: Spec mySpec = do describe "Prelude.head" $ do it "returns the first element of a list" $ do head [23 ..] `shouldBe` (23 :: Int) it "returns the first element of an *arbitrary* list" $ do property $ x xs -> head (x:xs) == (x :: Int) it "throws an exception if used with an empty list" $ do evaluate (head []) `shouldThrow` anyException https://hspec.github.io/
  • 32. share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persist| Person name String age Int Maybe deriving Eq Show BlogPost title String authorId PersonId deriving Eq Show Follow follower PersonId followed PersonId deriving Eq Show |] https://www.yesodweb.com/book/persistent
  • 33. recentArticles :: SqlPersistT m [(Entity User, Entity Article)] recentArticles = select . from $ (users `InnerJoin` articles) -> do on (users ^.UserId ==. articles ^.ArticleAuthorId) orderBy [desc (articles ^.ArticlePublishedTime)] limit 10 return (users, articles) https://mmhaskell.com/web-skills-5
  • 34. type UserAPI = "users" :> ReqBody '[JSON] User :> Post '[JSON] User :<|> "users" :> Capture "userId" Integer :> ReqBody '[JSON] User :> Put '[JSON] User https://docs.servant.dev/en/stable/tutorial/ApiType.html
  • 35. type UserAPI = "users" ( ReqBody '[JSON] User :> Post '[JSON] User :<|> Capture "userId" Integer :> ReqBody '[JSON] User :> Put '[JSON] User ) https://docs.servant.dev/en/stable/tutorial/ApiType.html
  • 36. “If you have a set of things and a means of combining them, then it’s a language.” https://parametri.city/blog/2018-12-23-language-oriented-software-engineering
  • 39. A DSL for forms https://github.com/lumihq/purescript-lumi-components https://lumihq.github.io/purescript-lumi-components/#/form
  • 40.
  • 41. $ git clone https://github.com/arthurxavierx/monadic-party-edsl.git $ cd monadic-party-edsl/forms $ make watch
  • 42. newtype Registration = Registration { email :: EmailAddress , password :: NonEmptyString }
  • 43. registrationForm :: FormBuilder _ RegistrationFormData Registration registrationForm = ado email <- indent "Email" Required $ focus _email $ validated (isValidEmail "Email") $ validated (nonEmpty "Email") $ textbox password <- indent "Password" Required $ focus _password $ validated (nonEmpty "Password") $ passwordBox in Registration { email , password }
  • 44. type RegistrationFormData = { email :: Validated String , password :: Validated String } _email :: forall a r. Lens' { email :: a | r } a _email = lens _.email _{ email = _ } _password :: forall a r. Lens' { password :: a | r } a _password = lens _.password _{ password = _ } isValidEmail :: Validator String EmailAddress -- isValidEmail :: String -> Either String EmailAddress
  • 45. registrationForm :: FormBuilder _ RegistrationFormData Registration registrationForm = ado email <- indent "Email" Required $ focus (lens _.email _{ email = _ }) $ validated (isValidEmail "Email") $ validated (nonEmpty "Email") $ textbox password <- indent "Password" Required $ focus (lens _.password _{ password = _ }) $ validated (nonEmpty "Password") $ passwordBox in Registration { email , password }
  • 46. email <- indent "Email" Required $ focus (prop (SProxy :: _ "email")) $ validated (isValidEmail "Email") $ textbox password <- indent "Password" Required $ focus (prop (SProxy :: _ "password")) $ validated (nonEmpty "Password") $ passwordBox _ <- withValue { password } -> indent "Password confirmation" Required $ focus _passwordConfirmation $ validated (pc -> if pc == fromValidated password then Right pc else Left "Passwords do not match." ) $ passwordBox in Registration { email , password }
  • 47. How does it work?
  • 48. newtype FormBuilder value result = FormBuilder ( value -> { edit :: ((value -> value) -> Effect Unit) -> UI , validate :: Maybe result } ) instance Applicative (FormBuilder props value)
  • 49. newtype FormBuilder props value result = FormBuilder ( props -> value -> { edit :: ((value -> value) -> Effect Unit) -> UI , validate :: Maybe result } ) instance Applicative (FormBuilder props value)
  • 50. type FormUI value = ((value -> value) -> Effect Unit) -> UI newtype FormBuilder props value result = FormBuilder (ReaderT (Tuple props value) (WriterT FormUI Maybe) result) instance Applicative (FormBuilder props value)
  • 51. textbox :: forall props. FormBuilder props String String passwordBox :: forall props. FormBuilder props String String switch :: forall props. FormBuilder props Boolean Boolean
  • 52. indent :: forall props value result . String -> RequiredField -> FormBuilder props value result -> FormBuilder props value result focus :: forall props s a result . Lens' s a -> FormBuilder props a result -> FormBuilder props s result
  • 53. validated :: forall props value result_ result . (result_ -> Either String result) -> FormBuilder props value result_ -> FormBuilder props (Validated value) result withValue :: forall props value result . (value -> FormBuilder props value result) -> FormBuilder props value result
  • 54. build :: forall props value result . FormBuilder props value result -> { value :: value , onChange :: (value -> value) -> Effect Unit | props } -> JSX revalidate :: forall props value result . FormBuilder props value result -> props -> value -> Maybe result
  • 56. Multiple DSLs for building complex forms
  • 57. newtype Wizard props value result = Wizard (Free (FormBuilder props value) result) derive newtype instance Monad (Wizard props value) step :: forall props value result . FormBuilder props value result -> Wizard props value result
  • 58. newtype TableFormBuilder props value result = ... instance Applicative (TableFormBuilder props value) column :: forall props row result . String -> FormBuilder props row result -> TableFormBuilder props row result table :: forall props row result . TableFormBuilder props row result -> FormBuilder props (Array row) (Array result)
  • 59. Language oriented programming interpreterA :: LanguageA -> LanguageB interpreterB :: LanguageB -> LanguageC interpreterC :: LanguageC -> LanguageD ···
  • 60. Language oriented programming Design a domain-specific language for the core application logic Write the application in the DSL Build interpreters to execute the DSL programs
  • 61. Language oriented programming Abstracting business problems as programming language problems, so that solutions are DSLs How to abstract things ⇔ how to split things up and join them back together
  • 62. Language building ≅ domain modelling
  • 63. https://parametri.city/blog/2018-12-23-language-oriented-software-engineering “[…] you cannot know what the DSL will be ahead of time, you have to evolve it alongside the concrete implementation.”
  • 66. data Syntax = ... semantics :: Syntax -> _ DSL ≝ model + syntax + semantics
  • 67. data Primitives = ... combinator :: _ -> Primitives -> Primitives interpreter :: Primitives -> _ DSL ≝ primitives + composition + interpretation
  • 68. Feasting on the host language Expressions Control flow Data types Effects Recursion, unfortunately?
  • 69. Shallow × deep embedding
  • 70. Shallow × deep embedding Who does the work, interpreters or constructors? Extending: new interpretations or new constructors? Deep is simple, but shallow is direct
  • 71. newtype FormBuilder props value result = FormBuilder ( props -> value -> { edit :: ((value -> value) -> Effect Unit) -> UI , validate :: Maybe result } ) Shallow embedding
  • 72. Shallow embedding Syntax is defined in terms of the semantic domain More flexibility for adding new combinators (under a specific interpretation) Adding a new interpreter might imply: ● adding some new set of constructors ● and/or refactoring the semantic domain to include the new interpretation, then rework all constructors
  • 73. data FormBuilder props value result where Textbox :: FormBuilder props String String Password :: FormBuilder props String String ··· Focus :: Lens' s a -> FormBuilder props a result -> FormBuilder props s result ··· Deep embedding
  • 74. More flexibility for adding new interpreters (for a specific set of constructors) Adding new constructors might imply reworking all interpreters Deep embedding
  • 75. “[…] The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety.” https://en.wikipedia.org/wiki/Expression_problemExpression problem
  • 77. class Semigroup a where (<>) :: a -> a -> a class Monoid a where mempty :: a
  • 78. data Document = Empty | Block [Block] | Inline [Inline] data Block = Button String | Header String | Paragraph Inline | ... data Inline = Text String | Strong String | Link URL String | ... instance Semigroup Document where Empty <> Empty = Empty Empty <> Block b = b Block b <> Empty = b ··· Block (Button l) <> Inline (Text s) = Block (Button (l <> s)) ··· instance Monoid Document where mempty = Empty
  • 79. myDocument :: Document myDocument = fold [ Block [ Header "Hello, world!" , Paragraph "Lorem ipsum dolor sit amet, consectetur ..." , Button "Click me" ] , Block [ Paragraph "Look! An image!" , Image "https://whatever.com/whatever.jpg" ] ]
  • 80. {-# LANGUAGE RebindableSyntax #-} myDocument :: Document myDocument = do Block [ Header "Hello, world!" , Paragraph "Lorem ipsum dolor sit amet, consectetur ..." , Button "Click me" ] Block [ Paragraph "Look! An image!" , Image "https://whatever.com/whatever.jpg" ] where (>>) = (<>)
  • 82. class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b -- :: f a -> f b -> f (a, b)
  • 83. data AppConfig = AppConfig { hostname :: String , port :: Int , emailKey :: String , emailPassword :: String } appConfig :: EnvConfig AppConfig appConfig = AppConfig <$> string "hostname" <*> int "port" <*> string "emailKey" <*> string "emailPassword"
  • 84. {#- LANGUAGE ApplicativeDo #-} appConfig :: EnvConfig AppConfig appConfig = do hostname <- string "hostname" port <- int "port" emailKey <- string "emailKey" emailPassword <- string "emailPassword" pure $ AppConfig { hostname = hostname , port = port , emailKey = emailKey , emailPassword = emailPassword }
  • 86. class Applicative m => Monad m where bind :: m a -> (a -> m b) -> m b
  • 87. data AppConfig = AppConfig { hostname :: String , port :: Int , emailKey :: String , emailPassword :: String , emailDefaultFrom :: Maybe String }
  • 88. appConfig :: EnvConfig AppConfig appConfig = do hostname <- string "hostname" port <- int "port" emailKey <- string "emailKey" emailPassword <- string "emailPassword" emailDefaultFrom <- if isJust emailKey then Just <$> string "emailDefaultFrom" else pure Nothing pure $ AppConfig { hostname = hostname , port = port , emailKey = emailKey , emailPassword = emailPassword , emailDefaultFrom = emailDefaultFrom }
  • 89. “[…] The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety.” https://en.wikipedia.org/wiki/Expression_problemExpression problem
  • 91. Tagless final Typeclasses as syntax Instances as semantics
  • 92. class Monoid d => MathDoc d where text :: String -> d (-) :: d -> d -> d -- subscripting (^) :: d -> d -> d -- superscripting (/) :: d -> d -> d -- fraction http://okmij.org/ftp/tagless-final/MathLayout.hs
  • 93. data LaTeX = LaTeX String instance Monoid LaTeX ... instance MathDoc LaTeX where text = LaTeX LaTeX a - LaTeX b = LaTeX (a ++ "_{" ++ b ++ "}") LaTeX a ^ LaTeX b = LaTeX (a ++ "^{" ++ b ++ "}") LaTeX a / LaTeX b = LaTeX ("frac{" ++ a ++ "}{" ++ b ++ "}") http://okmij.org/ftp/tagless-final/MathLayout.hs
  • 94. Tagless final Vertical extensibility: adding new interpreters Horizontal extensibility: adding new terms
  • 95. data LaTeX = LaTeX String instance MathDoc LaTeX where text = LaTeX LaTeX a - LaTeX b = LaTeX (a ++ "_{" ++ b ++ "}") LaTeX a ^ LaTeX b = LaTeX (a ++ "^{" ++ b ++ "}") LaTeX a / LaTeX b = LaTeX ("frac{" ++ a ++ "}{" ++ b ++ "}") instance MathDoc String where text = id a - b = a ++ "_" ++ b a ^ b = a ++ "^" ++ b a / b = a ++ "/" ++ b http://okmij.org/ftp/tagless-final/MathLayout.hs
  • 96. class MathDoc d where text :: String -> d (-) :: d -> d -> d -- subscripting (^) :: d -> d -> d -- superscripting (/) :: d -> d -> d -- fraction data GreekLetter d = GreekLetter Char alpha = greek $ GreekLetter 'ɑ' class MathDoc d => Greek d where greek :: GreekLetter -> d class MathDoc d => Circle d where circle :: d http://okmij.org/ftp/tagless-final/MathLayout.hs
  • 97. http://okmij.org/ftp/tagless-final/MathLayout.hs -- doc1 :: (Circle d, Greek d) => d doc1 = alpha - (text "1") ^ circle doc1_string :: String doc1_string = doc1 -- > "ɑ_1^º" doc1_latex :: LaTeX doc1_latex = doc1 -- > LaTeX "alpha_1^circ"
  • 98. Tagless final Composing constraints ≅ defining capabilities MTL uses tagless final for expressing effects Type classes can be problematic sometimes
  • 100. Goals ● Validate events in an environment ○ Purchase, registration, product viewing, etc. ● Perform operations after events ○ Sending emails, updating the database, etc.
  • 101. data Registration = Registration { firstName :: Text , lastName :: Text , email :: EmailAddress , password :: Password , country :: Country } data User = ... data BillingInfo = ... data Purchase = ... data Product = ...
  • 102. data Env = Env { envUser :: Maybe User ··· }
  • 103. businessRules = fold [ to buyProduct validatePurchase , to register validateRegistration , to viewProduct validateViewProduct , ··· , to buyProduct validateUserBilling , after buyProduct sendPurchaseEmail , ··· ] where validatePurchase :: Purchase -> _ ···
  • 104. {-# LANGUAGE RebindableSyntax #-} businessRules = do to buyProduct validatePurchase to register validateRegistration to viewProduct validateViewProduct ··· to buyProduct validateUserBilling after buyProduct sendPurchaseEmail ··· where (>>) = (<>) validatePurchase :: Purchase -> _ ···
  • 105. validatePurchase purchase = do user <- authenticate is (available (orderedQuantity `of_` purchase) (orderedProduct `of_` purchase)) exists (price `of_` orderedProduct `of_` purchase) exists (country `of_` user done where available q product = quantity `of_` product >= q validateUserBilling _ = do user <- authenticate exists (billingInfo `of_` user) done authenticate = do env <- getEnv exists (envUser env)
  • 106. What is a possible solution?
  • 107. Let’s first think about the constraints 1. Validation rules depend on an environment. 2. Validation rules are sequentially composable. 3. Business rules can be combined. 4. The input to a validation rule or to an effect after an event must match the event’s contents. 5. Dispatching an event to a set of business rules must give us back (maybe) an effect.
  • 108. data Event = BuyProduct Purchase | Register Registration | ViewProduct Product
  • 109. type Match f a = f -> Maybe a to :: Match event e -> (e -> Rules) -> Rules after :: Match event e -> (env -> e -> m a) -> Rules dispatch :: Applicative m => Rules -> env -> event -> Maybe (m ())
  • 110. is :: Bool -> Validation () exists :: Maybe a -> Validation a getEnv :: Validation env runValidation :: Validation a -> env -> Maybe a -- Syntax sugar infixr 9 `of_` of_ :: (a -> b) -> a -> b of_ = ($) done :: Monad m => m () done = return ()
  • 111. What if we wanted to depend on a database to perform validations? What if we wanted to add proper error messages? Try expressing rules with both deep and shallow embedding. What if we wanted to have a pipeline instead? That is, feeding validated outputs to after rules. Tip: one possible solution requires having only a single type of rule instead of the original two: to and after. Exercises
  • 112. Can we refactor Validation to be written in terms of classic monad transformers? How could we statically render validation rules as documentation text? How can we restrict the type of effects that can be performed after each event? Tip: you can either use records of effects or ConstraintKinds. Food for thought
  • 114. 4Da Capo al Coda
  • 116. $ git clone https://github.com/arthurxavierx/monadic-party-edsl.git $ cd monadic-party-edsl/chatbot $ npm ci $ make run
  • 117. FinalizedStart Answer Ask ε start answer ask finalize
  • 118. class IxFunctor f where imap :: (a -> b) -> f x y a -> f x y b class IxFunctor m => IxApplicative m where iapply :: m x y (a -> b) -> m y z a -> m x z b :: m x y a -> m y z b -> m x z (a, b) ipure :: a -> m x x a class IxApplicative m => IxMonad m where ibind :: m x y a -> (a -> m y z b) -> m x z b
  • 119. preferredLanguage = Ix.do start any answer "Hey there!" language <- askPreferences loop assertPreferences language answer "Nice!" finalize
  • 120. preferredLanguage = Ix.do start any answer "Which one do you prefer, Haskell or PureScript?" language <- ask parseLanguage loop language lang -> Ix.do if lang == PureScript then break unit else Ix.do answer "Errrr... Which one do you really prefer?" lang' <- ask parseLanguage continue lang' answer "Nice!" finalize where parseLanguage = do prefers <- (Haskell <$ match "haskell") <|> (PureScript <$ match "purescript") pure { prefers }
  • 122. Exercises How can we allow multiple consecutive answers and questions while keeping the initial and final transitions? How could we make the DSL strictly applicative? And what implications does this change effectively have? Tip: we’ll need to add two new combinators.
  • 123. Food for thought Can you think of a simpler way to express the same DSL? How would you improve the domain modelling? How would your changes affect the DSL?
  • 125. Language is the essence of abstraction. A language can be seen as a pair syntax + semantics. We can write tiny languages embedded in Haskell that correctly model a domain. Wrapping up
  • 126. We can make use of fundamental typeclasses such as Monoid, Applicative and Monad to embed a language more ergonomically and safely. We can define the syntax of a DSL in Haskell in terms of its abstract syntax or of its semantic domain. (deep × shallow embedding) Wrapping up
  • 127. Wrapping up We can use typeclasses and instances to embed extendable DSLs in Haskell. (tagless final style) We can leverage Haskell’s powerful type system to make our DSLs more expressive and safe. Creativity is key, but respecting laws is important.
  • 128. Separation of concerns High development productivity Highly maintainable design Less lines of less complex code ⇒ more maintainable code. Highly portable design Depending on the choices regarding embedding techniques. The good
  • 129. Opportunities for reuse User enhanceable systems Fewer bugs Improved adaptability The good
  • 130. The bad A hard design problem Up-front cost A tendency do use multiple languages Language cacophony Maintenance can sometimes be painful
  • 132. Monad transformers Free applicatives & free monads Foldable, Traversable, Alternative Type-level programming Functor combinators: Sum, Product, Compose, Day, Yoneda, Coyoneda, Alt Abstract interpretation Further reading
  • 133. Share your thoughts and conclusions!
  • 134. References Ward, M. P. (1994). Language Oriented Programming. Van Deursen, A. et al. (2000). Domain-specific languages: An annotated bibliography. Kiselyov, O. (2012). Typed Tagless Final Interpreters. Gibbons, J., & Wu, N. (2014). Folding Domain-Specific Languages: Deep and Shallow Embeddings.