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 Haskell 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. We derive Show so we can display sandwiches.
data Sandwich = Sandwich [Layer] deriving Show
Note: In this case study, we use the same name for both algebraic data types and constructors within them. Above the Sandwich after data defines a type and the one after the "=" defines the single constructor for that type.
Note: Sandwich is 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.
newSandwich :: Bread -> Sandwich addLayer :: Sandwich -> 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. Note that we use names such as Bread both as a constructor of the Layer type and the type of the ingredients within that category.
data Layer = Bread Bread | Meat Meat | Cheese Cheese | Vegetable Vegetable | Condiment Condiment deriving (Eq, Show) data Bread = White | Wheat | Rye deriving (Eq, Show) data Meat = Turkey | Chicken | Ham | RoastBeef | Tofu deriving (Eq, Show) data Cheese = American | Swiss | Jack | Cheddar deriving (Eq, Show) data Vegetable = Tomato | Onion | Lettuce | BellPepper deriving (Eq, Show) data Condiment = Mayo | Mustard | Ketchup | Relish | Tabasco deriving (Eq, Show)
We need to be able to compare ingredients for equality. Because the default definitions are appropriate, we derive both classes Show and Eq for these ingredient types. We did not derive Eq 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.
data Platter = Platter [Sandwich] deriving Show
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.
newPlatter :: Platter addSandwich :: Platter -> Sandwich -> Platter
Please put these functions in a Haskell module SandwichDSL. You may use functions defined earlier in the exercises to implement those later in the exercises.
isBread :: Layer -> Bool isMeat :: Layer -> Bool isCheese :: Layer -> Bool isVegetable :: Layer -> Bool isCondiment :: Layer -> Bool
noMeat :: Sandwich -> Bool
inOSO :: Sandwich -> Bool intoOSO :: Sandwich -> Bread -> Sandwich
priceSandwich :: Sandwich -> Int prices = [(Bread White,20),(Bread Wheat,30),(Bread Rye,30), (Meat Turkey,100),(Meat Chicken,80),(Meat Ham,120), (Meat RoastBeef,140),(Meat Tofu,50), (Cheese American,50),(Cheese Swiss,60), (Cheese Jack,60),(Cheese Cheddar,60), (Vegetable Tomato,25),(Vegetable Onion,20), (Vegetable Lettuce,20),(Vegetable BellPepper,25), (Condiment Mayo,5),(Condiment Mustard,4), (Condiment Ketchup,4),(Condiment Relish,10), (Condiment Tabasco,5) ]
eqSandwich :: Sandwich -> Sandwich -> Bool
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 data type SandwichOp below represents the instructions.
data SandwichOp = StartSandwich | FinishSandwich | AddBread Bread | AddMeat Meat | AddCheese Cheese | AddVegetable Vegetable | AddCondiment Condiment | StartPlatter | MoveToPlatter | FinishPlatter deriving (Eq, Show)
We also define the type Program to represent the sequence of commands resulting from compilation of a Sandwich or Platter specification.
data Program = Program [SandwichOp] deriving Show
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 [Bread Rye,Condiment Mayo,Cheese Swiss, Meat Ham,Bread Rye]
The corresponding sequence of SueChef commands would be the following.
[StartSandwich,AddBread Rye,AddMeat Ham,AddCheeseSwiss, AddCondiment Mayo,AddBread Rye,FinishSandwich,MoveToPlatter]
Correction: The original handout distributed in class on 27 October 2014 had the order of the ingredients reversed in the sequence of instructions.
compileSandwich :: Sandwich -> [SandwichOp]
compile :: Platter -> Program
UP to CSci 450/503 Lecture Notes root document?