PS1: Antics with Semantics
-
Clarifications shown in magenta.
-
Dueish:: Mon Nov 02
- Notes:
- As with all psets, you can work on this problem in a two-person team or as an individual. But you need to partner in a team on at least 3 psets during the semester.
- You can do Problems 1 through 3 right away.
- Problems 4 and 5 require only a little knowledge of Racket and some review of CS111-style Divide/Conquer/Glue fruitful recursive functions (see link to CS111 recursion lecture in Problem 5).
- You should wait until the lectures on big-step and small-step semantics before attempting Problem 6.
- The problems needn’t be done in order. Feel free to jump around.
- This pset has 100 points, but also includes a competely optional 12-point extra credit problem. You should only attempt the extra credit problem once you have finished the required problems. Extra credit problems must always be done by individuals, never by a team.
-
How to Start PS1
-
Follow the instructions in the GDoc CS251-F20-T2 Pset Submission Instructions for creating a PS1 Google Doc. Put all written answers and a copy of all code into this PS1 GDoc.
-
Create a file folder on your computer named yourCSAccountName_ps1 (e.g.,
gdome_ps1
). (If working on a two-person team, you can use one partner’s name or combine both partners names.) Below you will be asked to populate this file folder with various files on the assignment that you will submit (in the final step) to a drop folder oncs.wellesley.edu
.
-
-
Submission:
-
For all problems, include all answers (including Racket code and big-step/small-step derivations) in your PS1 GDoc. Please format your Racket function definitions in Problem 5 and your derivations in Problem 6 so that they’re easy to read. Format all derivations and code using a fixed-width font (Courier New or Consolas). You use a small font size if that helps.
-
For Problem 1 (Concrete Syntax), put your
updateScore.py
andupdateScore.js
files in youryourCSAccountName_ps1
folder on your computer. -
For Problem 5 (Racket Recursion), put your
ps1-functions.rkt
file in youryourCSAccountName_ps1
folder on your computer. -
Drop a copy of your
yourCSAccountName_ps1
folder into your~/cs251/drop/ps01
drop folder oncs.wellesley.edu
(a.k.a.tempest
). For transferring files to your CS251 drop folder, you may use a file transfer application like Cyberduck, but you can also usescp
from a Terminal window on your computer via the following command. (This assumes you are connected to the correct directory and have replaced both occurrences ofgdome
by yourtempest
username.)~~~ scp -r yourCSAccountName_ps1 gdome@cs.wellesley.edu:/students/gdome/cs251/drop/ps01 ~~~
-
1. Concrete Syntax (18 points)
This problem explores how the same program can be written with different concrete syntax in different languages.
Consider the following App Inventor program, which consists of two global variable declarations and the declaration of an updateScore
procedure. (This program is incomplete, since updateScore
is never called.)
Create two files, updateScore.py
and updateScore.js
, that contain, respectively, (1) Python and (2) JavaScript versions of this App Inventor program. These files must also include (1) testing code that shows the programs work as expected and (2) transcripts showing the result of running the test cases (as comments in the code).
Notes:
-
If you don’t know or can’t remember details of Python or JavaScript syntax, look it up on the Web!
-
The App Inventor example includes two global variable declarations (
score
andcolor
), a local procedure parameter declaration (points
), variable references (getters) for all of these, and variable assignments (setters) for the global variables. In one of your programs, you will need an extra declaration to distinguish global from local variables. Why? (You don’t need to answer this question in your solution, but should think about it.) -
You should translate App Inventor’s
get
block as a simple variable reference. E.g.get x
in App Inventor would translate to the variable referencex
in both languages. -
You should translate App Inventor’s
set
block as a simple variable assignment. E.g.set x 42
would translate tox = 42
in both languages. -
App Inventor includes both conditional statement blocks (the two left blocks below) and conditional expression blocks (the rightmost block below). Make sure you understand the difference between these and translate them appropriately.
In general, a statement is a program phrase that performs an action while an expression is a program phrase that returns a value. In App Inventor, statements compose vertically to emphasize that their actions are performed sequentially from top to bottom, while expressions have plugs on their left side that denote the value returned by the expression. This value is used by the block whose socket the plug is connected to.
Both Python and JavaScript have different syntax for conditional statements vs. conditional expressions. In your programs, you should use the appropriate syntax for each one. (If you don’t know it, look it up!) In particular, do not use the conditional statement syntax to translate App Inventor’s conditional expression block!
-
What App Inventor calls a “procedure” is called a “function” in Python and JavaScript. The term “function” often implies that callers for the entity can return a value (i.e., function calls are expressions), whereas “procedure” often implies that callers for the entity perform an action but do not return a value (i.e., procedure calls are statements). But the usage is rather inconsistent between languages.
-
You must test your
updateScore
functions to verify that they work correctly. E.g., you can use a Python intepreter to testupdateScore.py
and a JavaScript interpreter in the browser (or a standalone Node.js JavaScript intepreter) to testupdateScore.js
. You should show the inputs and outputs of all test cases. For full credit, you should use test cases that exercise all combinations of conditional branches in yourupdateScore
functions. -
In your testing, you need only consider inputs that are numbers. Don’t worry how the functions deal with non-numeric inputs.
BTW #1: App Inventor also has top-level declarations (blocks that introduce top-level named entities, like procedures and global variables). These are blocks that do not have any plugs or sockets and so cannot be connected to other blocks.
BTW #2: Racket only has declarations and expressions; it does not have statements. So Racket’s if
is a condtional expression and not a conditional statement. What’s it like to program in a language without statements? You’ll find out!
BTW #3: If you’re curious, you can learn more about App Inventor at the MIT App Inventor website and can experiment with building your own apps using the App Inventor development environment. You can even test your new apps on your own mobile device using the MIT AI2 Companion app:
-
If you have an Android device, you can test the apps you create on your device by downloading the MIT AI2 Companion from the Google Play Store on that device.
-
If you have an iOS device, there is a beta version of the Companion you can test. First you need to install the TestFlight app from the Apple app store on you device. Then go to this link on your device’s browser after installing TestFlight. This will download and install the iOS companion on your device.
2. The Evils of Two Lesses (15 points)
Consider the exact expression (3 < 4 < 2)
. (Do not remove the parens or any spaces, add any extra parens, or move around any symbols.) This expression is handled differently in different programming languages. For each of the following four programming languages, explain how this expression is handled in that language.
-
If the expression returns a value, specify that value and explain why that value is returned and how you know this fact. Give evidence from a sample program in that language that is has the value you claim. Your explanation can’t just be ``This is the value the interpreter for the language returned”. You have to explain why this particular value is returned according to the evaluation rules of the language.
-
If the expression causes an error, explain the nature of the error (is it a syntax error? static type error? dynamic type error? something else?) and how you know this fact. Again, give evidence from a sample program in that language that is has the error you claim.
- Java (4 points)
- JavaScript (4 points)
- Python (4 points)
- Racket (3 points)
Note: If the answer depends on special features of the language, you should cite a source for the documentation explaining those features.
3. Truthiness (16 points)
)
In the context of a conditional test, a value that chooses the then arm of the conditional is called truthy and a value that chooses the else arm of the conditional is called falsey (sometimes spelled falsy). We will use the term neither to refer to a value that cannot be used as a conditional test (causing a compile-time or run-time error).
We have seen that in Racket, the #f
value is falsey and every value that is not #f
is truthy; Racket has no neither values. This problem asks you to explore truthiness in other languages.
Below are five programming languages and links to their language specification documents. Based on information in the documents, determine which values are truthy, which are falsey, and which (if any) are neither. In your answers, explicitly cite all the sections of the reference manuals that you use to justify your answer. Points will be deducted if appopriate citations are missing.
-
Scheme (3 points): http://www.schemers.org/Documents/Standards/R5RS/r5rs.pdf (Scheme is the parent of Racket.)
-
Java (3 points): https://docs.oracle.com/javase/specs/jls/se8/jls8.pdf
-
Python (3 points): https://docs.python.org/2/reference/
-
JavaScript (a.k.a. EcmaScript) (3 points): http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
-
C (4 points): http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1570.pdf
Notes:
-
Hint: begin by searching for the terms if statement or conditional in the documents.
-
One of the purposes of this problem is to introduce you to language specification documents. These are the authoritative sources for answering any questions about the languages, even nit-picky ones. Although they can be somewhat dense, you should get into the habit of consulting them. For reasons you will understand after doing this problem, people who write/consult/cite such language specifications are sometimes called language lawyers.
-
In the EcmaScript specification, notions like ReturnIfAbrupt and CompletionRecord are used to model the dynamic semantics of exceptions and can be ignored for the purposes of this problem.
-
The correct answer for C is particularly challenging to track down. Consider the following in your answer:
- The
true
andfalse
macros defined in Sec 7.18 are not relevant for this problem - The C specification for the semantics of if statments uses the terminology “compares equal to 0”. Although it’s not obvious, this terminology is defined in Section 6.5.9 Equality operators on p. 96.
- The C specification for the semantics of if statements mentions “scalar types”. It’s important to understand what these are and what other types there are in C.
- The notion of conversion between values of different types (Se 6.3.2) plays a role in this problem.
- In C, a pointer is an abstraction over a memory address. Are there any pointers treated as falsey?
- How are arrays treated by C in the context of a test expression? Is there any conversion on arrays that might treat them as a non-neither value?
- Even after carefully considering all of the above, be warned that the situation may be as clear as mud. Give your best interpretation of the information that you find! <
- The
-
Compare the length of the Scheme reference document (only 50 pages) to the others. When people say that Scheme/Racket are simpler than other languages, this is one piece of evidence that they give.
4. Racket Evaluation (11 points)
For each of the following Racket definitions, write the value to which the variable will be bound. If evaluating the expression in the definition would give rise to an error, write error
, along with a brief explanation of why the error occurs.
Write your answers, with one line per definition, in order, in the form:
x |-> 7
y |-> 3
z: error -- attempt to divide a number by 0.
meaning that variable x
is bound to value 7
, variable y
is bound to value 3
, and evaluating the binding for z
results in a divide-by-zero error.
Predict the answers without running the code. You may find it useful to write evaluation derivations, leaving blanks for the result values as you build the derivation, and then filling in the results once you begin to reach values, but you do not need to submit any derivations in this problem. You may also check your manually evaluated answers by running each binding in order in the DrRacket REPL. (Instructions on using DrRacket are here.) It’s OK to change your answer if the interpreter shows that your predicted answer is incorrect. However, in this case, you should explain that your original prediction was wrong and your understanding of why the answer given by the interpreter is correct.
#lang racket
(define a 5)
(define b (* a a))
(define c (+ (* 2 a) (- b a)))
(define d (+ a b c))
(define e (+ a))
(define f (+))
(define g (*))
(define h b)
(define i (if (< a 0) 7 (quotient 43 a)))
(define j (if (= a 0) 11 (remainder 43 a)))
(define k (/ i j))
(define l (< a b))
(define m (= b c))
(define n (< b c d))
(define o (+ a m))
(define p (< a m) )
(define q (if a b c))
(define r (if l m n))
(define s (if (if (< i j) m l)
(if (> a c) (+ a c) (* a c))
(if (< a b) (+ a b) (* a b))))
(define t (= (if (< b c)
(* (remainder (- (* a b) (* 2 a)) c) (- (quotient d b)))
(* (- (+ d (* (* a b) (quotient d a))))))
(- (* (/ (- c a) (- a 1)) (quotient b (if (> a c) i j))))))
(define u (if (> i j) (< a 0) (< (< b c) a)))
(define v (if (<= i j) (< a 0) (< (< b c) a)))
5. Racket Recursion (20 points)
Although you’ve written recursive function definitions before in other courses, recursion is particularly important in CS251 for three reasons:
-
The list data structures in Racket and Standard ML are recursively defined, and so are naturally processed with recursion. (You will get lots of practice with these starting next week.)
-
Neither Racket nor Standard ML has looping constructs, so what you would express via loops in other languages must be expressed via recursion in these languages.
-
Later in the semester we will study metaprograms – i.s., programs that manipulate other programs. Metaprograms typically process the abstract syntax tree (AST) structure of the program being manipulated. Such tree processing is most naturally expressed using recursion. Indeed, you’ve already seen that big-step evaluation semantics is tree-recursive in nature.
For each of the two Racket function specifications below, write and test a recursive function that satisfies that specification. In all of your definitions, you should use the following recursive problem solving strategy:
-
For which argument(s) is the function so simple that the answer can be returned immediately? This is the base case.
-
For the other case(s) (known as the recursive case(s)), use divide/conquer/glue:
-
divide: make one or more subproblems that are smaller instances of the given problem;
-
conquer: assume that the recursive function you’re defining simply works and returns the correct answer on all of the smaller problems.
-
glue: combine the result(s) of the recursive function call(s) with information in the original problem to create the correct result for the whole problem.
-
Notes:
-
Before starting this problem, you may want to review these CS111 slides on fruitful recursion in Python. (A function is fruitful when it returns a value.)
-
Here are some simple recursive numerical functions written in Racket:
;; Return the factorial of n
(define fact
(lambda (n)
(if (= n 0)
1
(* n (fact (- n 1))))))
;; Return the nth Fibonacci number
(define fib
(lambda (n)
(if (<= n 1)
n
(+ (fib (- n 1)) (fib (- n 2))))))
;; Return the sum of the integers between lo and hi
(define sum-between
(lambda (lo hi)
(if (> lo hi)
0
(+ lo (sum-between (+ lo 1) hi)))))
-
For this problem, you should use Dr. Racket to create a single file named
yourCSAccountName-ps1-functions.rkt
that contains all the functions (including helper functions) that you define for this problem. -
In your definitions you should not introduce any recursive helper functions. (But you can define nonrecursive helper functions).
-
If the following error message pops up during the testing of one of your functions, it mostly likely means that you have an infinite recursion that doesn’t reach its base case and runs out of memory due to a stack depth that cannot fit into available memory.
-
(10 points) Define a function
sum-squares-of-ints-divisible-by
that takes three integer arguments (divisor
,lo
, andhi
) and returns the sum of the squares of all the integers betweenlo
andhi
(inclusive) that are evenly divisible bydivisor
.> (sum-squares-of-ints-divisible-by 2 1 8) 120 ; = 2^2 + 4^2 + 6^2 + 8^2 > (sum-squares-of-ints-divisible-by 3 1 10) 126 ; = 3^2 + 6^2 + 9^2 > (sum-squares-of-ints-divisible-by 5 1 10) 125 ; = 5^2 + 10^2 > (sum-squares-of-ints-divisible-by 7 1 10) 49 ; = 7^2 > (sum-squares-of-ints-divisible-by 11 1 10) 0 ; no multiples of 11 between 1 and 10 > (sum-squares-of-ints-divisible-by 11 7 15) 121 ; = 11^2 > (sum-squares-of-ints-divisible-by 2 10 8) 0 ; the range "from 10 up to 8" is empty
Use the following helper function in this problem:
(define divisible-by? (lambda (num divisor) (= (remainder num divisor) 0)))
Notes:
-
You may assume that all three inputs are integers. You need not worry about handling or testing inputs that are not integers.
-
Although not shown in the above examples, any of the integer inputs can be negative. So both
(sum-squares-of-ints-divisible-by 2 -4 4)
and(sum-squares-of-ints-divisible-by -2 -4 4)
should yield 40 =(-4)^2 + (-2)^2 + 0^2 + 2^2 + 4^2
and(sum-squares-of-ints-divisible-by 3 -10 -1)
should yield 126 =(-9)^2 + (-6)^2 + (-3)^2
.
-
-
(10 points) A Hamming number is any positive integer expressible as 2i⋅3j⋅5k. E.g., the Hamming numbers between 1 and 100 are:
1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36 40 45 48 50 54 60 64 72 75 80 81 90 96 100
Define a function
hamming?
that takes a single numeric argument (including 0, negatives, and nonintegers), and returns#t
if the argument is a Hamming number and#f
if it is not. Your function need not work on arguments that are not numbers.> (hamming? 30) #t > (hamming? 31) #f > (hamming? -31) #f > (hamming? 3.141) #f > (hamming? 0) #f > (filter hamming? (range -100 101)) ; list all integers from -100 up to ; (but not including) 101 ; for which hamming? is true '(1 2 3 4 5 6 8 9 10 12 15 16 18 20 24 25 27 30 32 36 40 45 48 50 54 60 64 72 75 80 81 90 96 100)
Notes:
- As with all recursive functions, use the divide/conquer/glue strategy to solve this one.
- You may assume that the input is a number. You need not worry about handling or testing inputs that are not numbers.
- Use
integer?
to test if a value is an integer. - Use
(and E1 E2 ... En)
to determine if all of the expressionsE1
,E2
, …,En
are true. - Use
(or E1 E2 ... En)
to determine if at least one the expressionsE1
,E2
, …,En
is true. - You needn’t use any
if
expressions in your definition. All you need areand
andor
. (But you can useif
if you want to.) - The
divisible-by?
helper function from Problem 5a may be useful here as well but is not required. There are correct solutions that do not usedivisible-by?
.
6. Big-step and Small-step Semantics (20 points)
Lec 04 and Lec 05 introduce two models for evaluation of Racket expressions:
-
The big-step evaluation model shows the evaluation of an expression as an evaluation derivation tree in which the evaluation of the whole expression is determined from the evaluation of its subexpressions.
-
The small-step evaluation model shows the evaluation of an expression as an step-by-step process in which the lefmost redex (reducible expression) is evaluated on each step until there are no more redexes.
As an example, consider evaluating the Racket expression (if (< x y) (* (- y 10) x) (/ y 0))
in an environment env2
in which x
is bound to 5 and y
is bound to 17. Then one way to write big-step evaluation derivation of this expression is
| | x # env2 ↓ 5 [varref]
| | y # env2 ↓ 17 [varref]
| -------------------- [less-than]
| (< x y) # env2 ↓ #t
|
| | | y # env2 ↓ 17 [varref]
| | | 10 # env2 ↓ 10 [value]
| | -------------------- [subtraction]
| | (- y 10) # env2 ↓ 7
| |
| | x # env2 ↓ 5 [varref]
| -------------------------- [multiplication]
| (* (- y 10) x) # env2 ↓ 35
----------------------------------------------- [if nonfalse]
(if (< x y) (* (- y 10) x) (/ y 0)) # env2 ↓ 35
In the above conclusion-below-subderivations format, each conclusion below a dotted line is justified by the subderivations above the dotted line. Another way to write this derivation is “upside down” using conclusion-above-subderivations format, where each conclusion at the top is justified by the bulleted subderivations below it:
(if (< x y) (* (- y 10) x) (/ y 0)) # env2 ↓ 35
[if nonfalse](< x y) # env2 ↓ #t
[less-than]x # env2 ↓ 5
[varref]y # env2 ↓ 17
[varref]
(* (- y 10) x) # env2 ↓ 35
[multiplication](- y 10) # env2 ↓ 7
[subtraction]y # env2 ↓ 17
[varref]10 # env2 ↓ 10
[value]
x # env2 ↓ 5
[varref]
A small-step derivation sequence for this example is shown below. Curly braces have been used to highlight the redex in the expression at each step. Rules names in square brackets show the rule used to reduce the redex in the previous line to the result in the current line.
(if (< {x} y) (* (- y 10) x) (/ y 0)) # env2
⇒ (if (< 5 {y}) (* (- y 10) x) (/ y 0)) # env2 [varref]
⇒ (if {(< 5 17)} (* (- y 10) x) (/ y 0)) # env2 [varref]
⇒ {(if #t (* (- y 10) x) (/ y 0))} # env2 [less-than]
⇒ (* (- {y} 10) x) # env2 [if nonfalse]
⇒ (* {(- 17 10)} x) # env2 [varref]
⇒ (* 7 {x}) # env2 [subtraction]
⇒ {(* 7 5)} # env2 [varref]
⇒ 35 # env2 [multiplication]
Despite differences between the big-step and small-step derivations, both are ultimately describing the same evaluation process, so they share lots of similarities:
- They evaulate the same subexpressions to the same values and get the same final result value for the whole expression.
- In both derivations, only the “then” clause of the
if
expression is evaluated. This is a good thing, since evaluating the “else” clause(/ y 0)
would give a division-by-zero error.
a. Big-step derivation (12 points)
Construct a big-step derivation for the following expression in an environment env5
with bindings a↦3, b↦6, c↦4:
(+ (if (< a b) (* a (+ b c)) (+ b #t))
(if (if (> a c) (< c b) (- c 4)) (* 2 b) a))
You may use either the conclusion-below-subderivations format or conclusions-above-subderivation format shown above — whichever you prefer. Regardless of format, you should indicate in brackets which rule is being used at each step of the derivation.
b. Small-step derivation (8 points)
Construct a small-step derivation for the same expression as in part a in the same environment, using the same formatting conventions used in the small-step derivation example above. In particular:
- Each step of the small-derivation should evaluate the leftmost redex, which should be highlighted between curly braces.
- The result of each step should be annotated with the rule name (in curly braces) used to reduce the redex from the previous line.
Extra Credit: Distinguishing Languages with One Expression (10 points)
This problem is optional. You should only attempt it after completing all the other problems.
In Problem 3, you saw that Racket, Python, JavaScript, and C all have slightly different notions of truthy and falsey. In this problem, the goal is to use this fact to design a single expression that denotes a string, and the string it denotes names the language in which the string is evaluated.
Of course, each of these four languages has a different syntax, so the “single expression” can’t be exactly the same in all the languages. Rather, it should have the same abstract syntax in all the languages but the concrete syntax can differ.
For example, consider these two conditional expressions:
- JavaScript:
((3 < 4 < 2) ? "javascript" : "python")
- Python:
"javascript" if 3 < 4 < 2 else "python"
They have different concrete syntax but the same abstract syntax. (Actually, (3 < 4 < 2)
is treated slightly differently in terms of abstract syntax trees in JavaScript and Pyton, but let’s ignore this detail for now.) But because of the difference in senantics between the languages, the JavScript expression evaluates to "javascript"
and the Python expression evaluates to "python"
.
Your goal is to write a single (abstract syntax) expression that can return one of the four strings "javascript"
, "python"
, "racket"
or "c"
, and when the (concrete syntax) of this expression is evaluated in JavaScript, Python, Racket, or C, it returns the name of the language in which it was evaluated. So effectively this expression is a test that answers the question “Which language I am in?”.
Notes:
-
You should give the concrete syntax of the same abstract expression in all four languages, and show a transcript from all four languages that it has the desired behavior.
-
You are allowed to use the following syntactic elements in your expression: numbers, strings, lists/arrays, and conditional expressions. You are not allowed to use operators (such as
+
,<
, etc.) or built-in functions. Because of the restriction on<
, you cannot use the example above to distinguish JavaScript and Python. -
For the purposes of this problem, Racket’s immutable lists, Python’s & JavaScript’s mutable lists, and C’s arrays will be considered “equivalent”. E.g. Racket’s list
'(1 2 3)
, Python’s & JavaScript’s list[1, 2, 3]
, and C’s array{1, 2, 3}
will be considered “the same”. -
For the C expression, you may (1) declare arrays outside of the expression that are used inside the expression and (2) may use explicit type casts to cast arrays to a type suitable for truthy/falsey.
-
You can get partial credit for distinquishing a subset of the languages: 2 points for distinquishing two languages, 5 points for distinguishin three languages, and 10 points for distinguishing all four.