From ideas to code
As developers we are faced with problems that we need to solve and then implement using a programming language and a computer.
The need to
-
Understand the problem.
-
Figure out the important information
-
Figure out how to use the information in order to get a solution through a well defined set of tasks
-
-
Translate information from the real world into data that the computer will use
-
Encode the tasks in the correct order to manipulate our data and get the solution
-
Verify that our encoding does in fact solve the problem
-
Can we improve/simplify our solution/encoding
We will follow a recipe that will guide and help us transcribe our solutions into code.
The Design Recipe
-
Data Design
-
Signature and Purpose
-
Examples and Tests
-
Function Definition
-
Program Review
Each step produces output that is either
-
comments in your source file
-
code in your source file
Follow the steps in order. No exceptions!
We will re-implement one of the problems in Lab 1 using the Design Recipe. We first present the contents of our source file and then take each step of the Design Recipe (DR) and explain in details what needs to be done as well as the output for that step.
;;;; Data Definitions:
;; A BillAmount is a Number
;; INTERP: represents the bill total amount in dollars and cents
;; A Percentage is a Number
;; INTERP: represents the percantage to be applied as a tip
;; A Tip is a Number
;; INTERP: represents the total amount to be given as tip in dollars and cents
;;;; Signature
;; tip-percentage: BillAmount Percentage -> Tip
;;;; Purpose
;; GIVEN: the bill's total amount and the percentage that we
;; would like to give as a tip
;; RETURNS: the amount given as tip
;;;; Examples
;; (tip-percentage 100 10) => 10
;; (tip-percentage 200 15) => 30
;;;; Function Definition
(define (tip-percentage bill percentage)
(* bill
(/ percentage 100)))
;;;; Tests
(check-expect (tip-percentage 100 10) 10)
(check-expect (tip-percentage 200 15) 30)
Data Design
Task
The first step in the DR is to understand the problem’s important pieces of information. We use the word information here to refer to the concepts used in the problem statement. These concepts are going to be described using English and refer to what we as humans understand.
For every problem ask yourself
-
What part of the information in the problem is important?
-
How is that information relevant to the solution?
We need to represent these piece of information as data. The word data is used to refer to information that lives our program and by extension in the computer.
Data definitions map information to data. We also must document the inverse relationship; how to go from data to information. The inverse relationship we call interpretation and we must document our interpretation of the data as well.
Example
In our tip example, the relevant pieces of information are
-
bill amount
-
tip
-
percentage
These are English terms that mean something to us. We need to capture these pieces of information as data. We use data definitions to create a name for a category of data that together hold some commons characteristics.
We know that in Beginning Student Language (BSL) we have numbers. For the purpose of this class we are providing you with a set of predefined data definitions that you can directly use without providing a definition.
We decide to represent
-
bill amount as a
Number
-
tip as a
Number
-
percentage as a
Number
Our data definition section created new data definition names (as aliases) for each piece of information.
;;;; Data Definitions:
;; A BillAmount is a Number
;; INTERP: represents the bill total amount in dollars and cents
;; A Percentage is a Number
;; INTERP: represents the percentage to be applied as a tip
;; A Tip is a Number
;; INTERP: represents the total amount to be given as tip in dollars and cents
For each piece of information we have
-
a Data Definition showing how we represent information as data
-
an
INTERP
line showing how we interpret data as information
We need to have both relationships between information and data in our code.
Signature
Purpose Statement
Task
The goal do the purpose statement is to provide a description of what the function does, not how it does it, by using English and referring to information and not data. The purpose statement should explain to a non-developer what this function does using the same concepts as the problem statement.
Examples
Tests
Task
The goal of this step is to create tests for the proposed function (we have not implemented the function yet). These are valid code and these are the tests that we are going to run in order to convince ourselves that our proposed function solves the problem. We must ensure that we have tests for as many possible cases as we can.
Typically the result of the step Examples are turned into tests and extra tests on top of those found in the Examples step of the DR are added to ensure that we have "no black lines" in our function’s definition.
Review
Task
The goal of this step is to go back to the beginning of our DR and perform the steps again but this time to ensure that
-
We have the correct Data Definitions and the names for any defined Data Definitions are good names
-
The Signature is correct according to our Function Definition and uses the correct Data Definition Names
-
The Purpose Statement is correct given our Function Definition and makes sense given our Problem Statement
-
Our Examples are correct given our Function Definition
-
Our Tests are correct and take care of as many cases as we can from the Problem Statement. We have no black lines in our Function Definition
While validating our DR steps we should also consider
-
Are my Data Definition too broad? Can I make my Data Definitions capture exactly what is needed?
-
Is my Signature too broad?
-
Can my Purpose Statement be clearer? Can it be simplified or re-written to avoid any confusion?
-
Are my Examples helpful? Do my Examples show how the function is supposed to behave? Are there other examples that better show the behaviour of my function?
-
Do my Tests cover as many cases as we can for the Problem Statement? Do we have any black lines that we need to address?
-
Can I simplify my Function Definition? Is my Function Definition too long? Is my Function Definition formatted correctly? Are the names used for arguments "good names"?
Example
Let’s review our solution for tip-percentage
Data Definitions
We have interpreted the bill amount as a Racket Number
. However, Number
in Racket allows for negative numbers, complex numbers etc.
This data definition seems too broad, the bill amount should not be a complex number, nor a negative number. We would like to allow for 0
as the bill amount though and it looks like there is a better, more accurate Data Definition, NonNegReal
.
For the percentage to be used, we have a similar situation, Number
is too broad. For percentage we would like to allow integers greater or equal to 0
. NonNegReal
seems to be a better choice here as well.
The tip, the result of our function, again Number
seems too broad. NonNegReal
seems to be a better choice here.
Signature
Our signature is correct, we take in 2 inputs. Since we created our own data definition names, our update from Number
to NonNegReal
does not affect our signature.
Purpose
The wording for RETURNS
seems a little off. It should read
;;;; Purpose
;; GIVEN: the bill's total amount and the percentage that we
;; would like to give as a tip
;; RETURNS: the amount to be given as tip
Tests
We might want to add a couple of more tests that use real numbers.
;;;; Tests
(check-expect (tip-percentage 100 10) 10)
(check-expect (tip-percentage 200 15) 30)
(check-expect (tip-percentage 10.15 5) 0.5075)
(check-expect (tip-percentage 25 10.5) 2.625)
End Result after Review
;;;; Data Definitions:
;; A BillAmount is a NonNegReal
;; INTERP: represents the bill total amount in dollars and cents
;; A Percentage is a NonNegReal
;; INTERP: represents the percantage to be applied as a tip
;; A Tip is a NonNegReal
;; INTERP: represents the total amount to be given as tip in dollars and cents
;;;; Signature
;; tip-percentage: BillAmount Percentage -> Tip
;;;; Purpose
;; GIVEN: the bill's total amount and the percentage that we
;; would like to give as a tip
;; RETURNS: the amount to be given as tip
;;;; Examples
;; (tip-percentage 100 10) => 10
;; (tip-percentage 200 15) => 30
;;;; Function Definition
(define (tip-percentage bill percentage)
(* bill
(/ percentage 100)))
;;;; Tests
(check-expect (tip-percentage 100 10) 10)
(check-expect (tip-percentage 200 15) 30)
(check-expect (tip-percentage 10.15 5) 0.5075)
(check-expect (tip-percentage 25 10.5) 2.625)
Different Kinds of Data
-
Scalar Data
-
Single values or simple data,
Number
,Boolean
etc.
-
-
Itemization Data
-
Fixed set of mutually exclusive options.
-
-
Compound Data
-
Data that we treat as a Unit but contain a fixed number of sub-components
-
-
Mixed Data
-
Combination of the previous three kinds of data
-
-
Recursive
-
Mutually Recursive
Predefined
We have some predefined data definitions that you can use.
Data Design Recipe
Data and Data Definitions are central to designing your programs. We are going to introduce more and more complex data throughout the course. To help us describe, create and decompose data definitions we have a Data Design Recipe.
-
Data Definition
-
Constructor Template
-
How do we create an instance of the Data Definition?
-
-
Interpretation
-
Data → Information
-
-
Deconstructor Template
-
Given an instance of this Data Definition how do we take it apart?
-
-
Examples
Itemizations
Itemization Data describe data that captures a selection (one and only one) from a given fixed set of options.
For example, consider the problem of mapping your number grade to a letter grade. A letter grade is one of
-
A
-
A-
-
B+
-
B
-
B-
-
C+
-
C
-
C-
-
D
Data Definition
We can create a data definition for Letter Grades as follows
;; A LetterGrade is one of
;; - 'A
;; - 'A-
;; - 'B+
;; - 'B
;; - 'B-
;; - 'C+
;; - 'C
;; - 'C-
;; - 'D
;; INTERP: represents a letter grade for course assignments
This data definition explicitly states that a letter grade is represented as one of a specific set of Symbol
s. It is not any Symbol
but it has
to be one of the Symbol
s written down in our data definition.
Constructor Template
To create a LetterGrade
we simply create a Symbol
, one of the Symbol
's that is a LetterGrade
. Given that this is scalar there is no
special way to create a LetterGrade
. We will see later examples that will have constructor templates.
Deconstructor Template
Given an instance of a LetterGrade
how are we going to take it apart? The words "take it apart" might not be appropriate for this example since LetterGrades
are Symbol
s and thus scalar. But we do need to figure out which exact LetterGrade
we were given, so we are in a way deconstructing the
LetterGrade
data definition.
The way we write the deconstructor is as an "incomplete" or template and place …
in our function definition for expression that we do not
know. To put another way, we are writing a function for which we do not know the output.
;; Deconstructor Template
;; letter-grade-fn: LetterGrade -> ??? (1)
#;(define (letter-grade-fn letter-grade)
(cond
[(symbol=? letter-grade 'A) ...]
[(symbol=? letter-grade 'A-) ...]
[(symbol=? letter-grade 'B+) ...]
[(symbol=? letter-grade 'B) ...]
[(symbol=? letter-grade 'B-) ...]
[(symbol=? letter-grade 'C+) ...]
[(symbol=? letter-grade 'C) ...]
[(symbol=? letter-grade 'C-) ...]
[(symbol=? letter-grade 'D) ...]))
1 | We write a signature, just like we would have done for a function, but we do not know what the return value is so we write ??? in our signature |
Similarly, trying to capture Number Grades we can create a data definition
;; A NumberGrade is an Integer in the range [0,100]
;; INTERP: represents the numerical grade given to a course assignment
NumberGrade
is a kind of itemization but we used [1,100]
which is a mathematical notation for the numbers from 0
to 100
inclusive instead of
writing all the numbers from 0
to 100
one number per line like we did for LetterGrade
. Similarly, we do not write a deconstructor template since
this is a range of numbers and we assume that we are all comfortable with deconstructing a range of numbers from math.
Let’s recreate our function from last week that translated our Number Grades to Letter Grades, this time of course using the Design Recipe
;;;; Data Definitions
;; A LetterGrade is one of
;; - 'A
;; - 'A-
;; - 'B+
;; - 'B
;; - 'B-
;; - 'C+
;; - 'C
;; - 'C-
;; - 'D
;; INTERP: represents a letter grade for course assignments
;; Deconstructor Template
;; letter-grade-fn: LetterGrade -> ???
#;(define (letter-grade-fn letter-grade)
(cond
[(symbol=? letter-grade 'A) ...]
[(symbol=? letter-grade 'A-) ...]
[(symbol=? letter-grade 'B+) ...]
[(symbol=? letter-grade 'B) ...]
[(symbol=? letter-grade 'B-) ...]
[(symbol=? letter-grade 'C+) ...]
[(symbol=? letter-grade 'C) ...]
[(symbol=? letter-grade 'C-) ...]
[(symbol=? letter-grade 'D) ...]))
;; A NumberGrade is an Integer in the range [0,100]
;; INTERP: represents the numerical grade given to a course assignment
;;;; Signature
;; number-grade->letter-grade: NumberGrade -> LetterGrade
;;;; Purpose
;; GIVEN: a numerical grade of an assignment
;; RETURNS: the corresponding letter grade
;;;; Examples
;;(number-grade->letter-grade 100) => 'A
;;(number-grade->letter-grade 94) => 'A-
;;(number-grade->letter-grade 76) => 'B-
;;;; Function Definition
(define (number-grade->letter-grade number-grade)
(cond
[(>= 100 number-grade 95) 'A]
[(>= 94 number-grade 90) 'A-]
[(>= 89 number-grade 85) 'B+]
[(>= 84 number-grade 80) 'B]
[(>= 79 number-grade 75) 'B-]
[(>= 74 number-grade 70) 'C+]
[(>= 69 number-grade 65) 'C]
[(>= 64 number-grade 60) 'C-]
[(>= 59 number-grade) 'D]))
;;;; Tests
(check-expect (number-grade->letter-grade 100) 'A)
(check-expect (number-grade->letter-grade 93) 'A-)
(check-expect (number-grade->letter-grade 88) 'B+)
(check-expect (number-grade->letter-grade 83) 'B)
(check-expect (number-grade->letter-grade 76) 'B-)
(check-expect (number-grade->letter-grade 72) 'C+)
(check-expect (number-grade->letter-grade 66) 'C)
(check-expect (number-grade->letter-grade 61) 'C-)
(check-expect (number-grade->letter-grade 55) 'D)