Suppose Emerald de Gassy, the owner of the Oxford-based catering business Deli-Gate, hires us to design a domain-specific language (DSL) for describing sandwich platters. The DSL scripts will direct Deli-Gate's robotic kitchen appliance SueChef (Sandwich and Utility Electronic Chef) to assemble platters of sandwiches.
In discussing the problem with Emerald and the Deli-Gate staff, we discover the following:
Let's define this as an internal DSL--in particular, by using a relatively deep embedding.
What is a sandwich? ... Basically, it is a stack of ingredients.
Should we require the sandwich to have a bread on the bottom? ... Probably. ... On the top? Maybe not, to allow "open-faced" sandwiches. ... What can the SueChef build? ... We don't know at this point, but let's assume it can stack up any ingredients without restriction.
For simplicity and flexibility, let's define a Scala data type Sandwich to model sandwiches. It wraps a possibly empty list of ingredient layers. We assume the head of the list to be the layer at the top of the sandwich.
case class Sandwich(sandwich: List[Layer])
Note: In this case study, we implement Scala algebraic data type
constructors (i.e., product types) as case class or
case object entities. We implement union types using a
trait with subtypes for the variants.
Data type Sandwich gives the specification for a sandwich. When "executed" by the SueChef, it results in the assembly of a sandwich that satisfies the specification.
As defined, the Sandwich data type does not require there to be a bread in the stack of ingredients. However, we add function newSandwich that starts a sandwich with a bread at the bottom and a function addLayer that adds a new ingredient to the top of the sandwich. We leave the implementation of these functions as exercises.
def newSandwich(b: Bread): Sandwich
def addLayer(s: Sandwich)(x: Layer): Sandwich
Ingredients are in one of five categories: breads, meats, cheeses,
vegetables, and condiments. Because both the categories and the
specific type of ingredient is important, we choose to represent both
in the type structures and define the following types. A value of type
Layer represents a single ingredient. Type
Layer has five variants (subtypes) Bread,
Meat, Cheese, Vegetable, and
Condiment. Each of these variants itself has several
variants. For example, Bread has variants (subtypes)
White, Wheat, and Rye.
sealed trait Layer
sealed trait Bread extends Layer
case object White extends Bread
case object Wheat extends Bread
case object Rye extends Bread
sealed trait Meat extends Layer
case object Turkey extends Meat
case object Chicken extends Meat
case object Ham extends Meat
case object RoastBeef extends Meat
case object Tofu extends Meat
sealed trait Cheese extends Layer
case object American extends Cheese
case object Swiss extends Cheese
case object Jack extends Cheese
case object Cheddar extends Cheese
sealed trait Vegetable extends Layer
case object Tomato extends Vegetable
case object Onion extends Vegetable
case object Lettuce extends Vegetable
case object BellPepper extends Vegetable
sealed trait Condiment extends Layer
case object Mayo extends Condiment
case object Mustard extends Condiment
case object Ketchup extends Condiment
case object Relish extends Condiment
case object Tabasco extends Condiment
We need to be able to compare ingredients for equality and convert them to strings. Because the automatically generated definitions are appropriate, we do not need to do anything further. We will need to provide an appropriate definition of equality for Sandwich because the default element-by-element equality of lists does not seem to be the appropriate equality comparison for sandwiches.
To complete the model, we define type Platter to wrap a list of sandwiches.
case class Platter(platter: List[Sandwich])
We also define functions newPlatter to create a new Platter and addSandwich to add a sandwich to the Platter. We leave the implementation of these functions as exercises.
def newPlatter: Platter
def addSandwich(p: Platter)(s: Sandwich): Platter
Please put these functions in a Scala module SandwichDSL. You may use functions defined earlier in the exercises to implement those later in the exercises.
def isBread(x: Layer): Boolean def isMeat(x: Layer): Boolean def isCheese(x: Layer): Boolean def isVegetable(x: Layer): Boolean def isCondiment(x: Layer): Boolean
def inOSO(s: Sandwich): Boolean def intoOSO(s: Sandwich)(defaultbread: Bread): Sandwich
def priceSandwich(s: Sandwich): Int
val prices = List(
(White,20),(Wheat,30),(Rye,30),
(Turkey,100),(Chicken,80),(Ham,120),(RoastBeef,140),(Tofu,50),
(American,50),(Swiss,60),(Jack,60),(Cheddar,60),
(Tomato,25),(Onion,20),(Lettuce,20),(BellPepper,25),
(Mayo,5),(Mustard,4),(Ketchup,4),(Relish,10),(Tabasco,5)
)
def eqSandwich(sl: Sandwich)(sr: Sandwich): Boolean
In this section, we look at compiling the Platter and Sandwich descriptions to issue a sequence of commands for the SueChef's controller.
The SueChef supports the special instructions that can be issued in sequence to its controller. The algebraic data type SandwichOp below represents the instructions.
sealed trait SandwichOp
case object StartSandwich extends SandwichOp
case object FinishSandwich extends SandwichOp
case class AddBread(bread: Bread) extends SandwichOp
case class AddMeat(meat: Meat) extends SandwichOp
case class AddCheese(cheese: Cheese) extends SandwichOp
case class AddVegetable(vegetable: Vegetable) extends SandwichOp
case class AddCondiment(condiment: Condiment) extends SandwichOp
case object StartPlatter extends SandwichOp
case object MoveToPlatter extends SandwichOp
case object FinishPlatter extends SandwichOp
We also define the type Program to represent the sequence of commands resulting from compilation of a Sandwich or Platter specification.
case class Program(program: List[SandwichOp])
The flow of a program is given by the following pseudocode:
StartPlatter
for each sandwich needed
StartSandwich
for each ingredient needed
Add ingredient on top
FinishSandwich
MoveToPlatter
FinishPlatter
Consider a sandwich defined as follows:
Sandwich(List(Bread Rye,Condiment Mayo,Cheese Swiss,
Meat Ham,Bread Rye))
The corresponding sequence of SueChef commands would be the following.
List(StartSandwich,AddBread Rye,AddMeat Ham,AddCheeseSwiss,
AddCondiment Mayo,AddBread Rye,FinishSandwich,MoveToPlatter)
def compileSandwich(s: Sandwich): List[SandwichOp]
def compile(p: Platter): Program
UP to CSci 555 Lecture Notes root document?