spring23/a8
directory.
a8/tester
(and a8/t
).
There's only one ASSIGNMENT-WIDE RESTRICTION: You may not require
(i.e., import) any modules.
In the examples that use rk/i
, I've elided the file name that appears before XREPL's >
prompt,
so instead of seeing examples like
"let-list.rkt"> (let-list h t '(a b) t) '(b)you'll see
> (let-list h t '(a b) t) '(b)
rk/loop
I hesitated to mention it in the a7
write-up because it's a little bit tricky
but during office hours I've shown a number of students
the rk/loop
script and they've had no trouble with it.
It's like rk/i
but it loops: whenever you hit control-D, it exits
and restarts racket
. It looks like this:
% rk/loop hello.rkt Note: one-second window after ^D and Reloading... to hit ^C Welcome to Racket v8.5 [cs]. Hello! (v1) "hello.rkt"> [edit hello.rkt, changing "v1" to "v2" and then type ^D] Reloading hello.rkt Welcome to Racket v8.5 [cs]. Hello! (v2) "hello.rkt"> [edit hello.rkt, changing "v2" to "v3" and then type ^D] Reloading hello.rkt Welcome to Racket v8.5 [cs]. Hello! (v3) ...
Thus, the edit-run cycle is edit-^D
-edit-^D
-edit-^D
..., much like
Eclipse with edit-F11
-edit-F11
-edit-F11
...
The tricky part with rk/loop
is that if you want to get out of it, perhaps because
you're ready to run the Tester, you've got a one-second window to hit
^C
(killing rk/loop
) after you hit ^D
and it prints Reloading...
.
If you miss that one-second window, you can simply try again (and again).
If you just can't seem to get out, you can always do ^Z
to suspend rk/loop
as a last resort. You'll see Stopped...
but that's a
misnomer—the "job" is only suspended. Use kill -9 %%
to kill it:
% rk/loop hello.rkt ... "hello.rkt"> Reloading hello.rkt ^Z [1]+ Stopped rk/loop hello.rkt % kill -9 %% [1]+ Killed rk/loop hello.rkt %
uniq.rkt
Note: Don't overlook the restriction below!
With the -c
option, the UNIX utility uniq
counts consecutive occurrences of lines:
% cat uniq.1 apple banana banana apple apple apple carrot carrot % uniq -c < uniq.1 1 apple 2 banana 3 apple 2 carrot
% racket uniq.rkt < uniq.1 1 apple 2 banana 3 apple 2 carrot
If the input is empty, uniq.rkt
produces no output.
% racket uniq.rkt < /dev/null %
As you may have learned in 352, if uniq -c
is given sorted input, it tabulates, and
thus so does uniq.rkt
:
% echo to be or not to be | fold -1 | sort | racket uniq.rkt 5 2 b 2 e 1 n 4 o 1 r 3 t
There's an interesting story involving Donald Knuth, Doug McIlroy, and uniq -c
. Take a look at matt-rickard.com/instinct-and-culture
for a short version of it.
RESTRICTION: Your solution must be based on folding. You may not write any recursive code,
use named let
, or use any sort of looping special form like any of the many variants of
for
s, or do
.
Just like wc.rkt
on slide 97 and minmax.rkt
on assignment 7,
uniq.rkt
reads from standard input. My solution uses
(port->lines)
to read all the lines on standard input and return a list of
them, just like sys.stdin.readlines()
in Python.
showfacts.rkt
The problem morefacts.txt
on assignment 2 asked you and your classmates to write some facts/opinions about three languages
of their choice. A compilation was posted in spring23/morefacts-s23.txt
.
For this problem you are to write a Racket program that reads a file with a compilation of language facts
and lets the user make queries. Running showfacts.rkt
causes it to load a8/all.sf
by
default. Once loaded, the user can run the procedure langs
to show the names of all the languages:
% rk/i showfacts.rkt Welcome to Racket v8.5 [cs]. > (langs) Languages: ABC, ACcent, APL, Action!, Apache Pig Latin, AppleScript, Arduino, Assembly, AutoHotkey, Autocode, B, Babbage, Ballerina, Bash, ...lots more... VimScript, Visual Basic, Whitespace, Wolfram Mathematica, Zig
The procedure facts
prints the facts/opinions about a specified language, preceded by the cited year(s) of origin.
The language can be specified with a string or a symbol and is case-insensitive. Facts are shown in lexicographic order.
> (facts 'eiffel) === Eiffel === Year(s): 1985, 1986 ---------- An object-oriented programming langauge. Many of the concpets in Java and C# were initially introduced in Eiffel. -- anonymous ---------- The goal of Eiffel was to increase the reliability of commercial software development. -- Anonymous > (facts "MIRANDA") === Miranda === Year(s): 1982 ---------- Functional programming lang that influenced Haskell --anonymous > (facts 'mooranda) ; perhaps a version of Miranda for cows... mooranda: not found
A different collection of facts can be loaded with the load
procedure. a8/5.sf
contains only five entries:
> (load "a8/5.sf") > (langs) Languages: Elixir, Futhark, J, Nim, SQL > (facts 'futhark) === Futhark === Year(s): 2014 ---------- A language in the ML family that was partially derived from Haskell. -- anonymous
If called with no arguments, facts
prints the facts for all the languages. Given that the
(load "a8/5.sf")
above is most recent, there are only five to show:
> (facts) All Facts: === Elixir === Year(s): 2012 ---------- A functional language based on the Erlang VM intended to create distributed and fault-tolerant systems. -- Anonymous === Futhark === Year(s): 2014 ---------- A language in the ML family that was partially derived from Haskell. -- anonymous === J === Year(s): 1996 ---------- It is a high-level, mathematical programming language good for creating algorithms and analyzing problems. It is written in portable C. -- anonymous === Nim === Year(s): 2008 ---------- general-purpose, multi-paradigm, statically typed, compiled systems programming language. Can compile everything from C to JavaScript. Illustrates you don't need to sacrifice performance for expressiveness === SQL === Year(s): 1970 ---------- it is used for managing relational databases and performing action on the data. -- Surinder Singh
showfacts.rkt
% cat a8/simple.sf Y 2011: Something about Y. X 2005: A poorly named language. Y 2012: Y is statically typed.Each line in all of the input files has a language name followed by a four-digit year of origin, immediately followed by a colon and then some sort of fact or opinion about the language.
Assume that all lines in all input files are well-formed. You won't see lines with two-digit dates or
missing dates. All lines have exactly one colon. (Hint: since there's just one colon, you can use
string-split
to separate the name and year from the blurb.)
The big, all-inclusive input file is a8/all.sf
. Browse through it.
Along with simple.sf
shown above, the a8
directory also has
5.sf
and 10.sf
, which are 5- and 10-line collections of facts/opinions.
As mentioned above, running showfacts.rkt
causes it to load a8/all.sf
by
default. Do that by having
(load "a8/all.sf")at the end of your
showfacts.rkt
.
Let's look at the central data structure, an association list named langs-alist
,
that my solution creates for a8/simple.sf
:
> (load "a8/simple.sf") > langs-alist '(("X" . #((2005) ("A poorly named language."))) ("Y" . #((2012 2011) ("Y is statically typed." "Something about Y.")))) > (length langs-alist) 2
We see that langs-alist
has two dotted-pairs, one for X and one for Y. Let's do a case-insensitive
lookup for Y:
> (assoc "y" langs-alist string-ci=?) '("Y" . #((2012 2011) ("Y is statically typed." "Something about Y.")))The
cdr
of that dotted-pair for Y, which assoc
returned, is a vector with two elements.
Let's bind v
to that vector and then explore v
:
> (define v (cdr (assoc "y" langs-alist string-ci=?))) > v '#((2012 2011) ("Y is statically typed." "Something about Y.")) > (vector-length v) 2 > (vector-ref v 0) '(2012 2011) > (vector-ref v 1) '("Y is statically typed." "Something about Y.")The first element of
v
is a list of years. Let's add the year 2010
to it:
> (vector-set! v 0 (cons 2010 (vector-ref v 0)))We see that change reflected in
v
and, most importantly, langs-alist
:
> v '#((2010 2012 2011) ("Y is statically typed." "Something about Y.")) > langs-alist '(("X" . #((2005) ("A poorly named language."))) ("Y" . #((2010 2012 2011) ("Y is statically typed." "Something about Y."))))
The process above, of finding an association list entry for a language and adding entries to the list is perhaps the crux of this problem. You'll also need to think about the case of creating the first association list entry for a language.
(load "a8/simple.sf")
and then worked with langs-alist
,
the association list my code created from a8/simple.sf
.
However, the example doesn't work if I create langs-alist
with a literal! Observe and weep:
> (define langs-alist '(("X" . #((2005) ("A poorly named language."))) ("Y" . #((2012 2011) ("Y is statically typed." "Something about Y."))))) > (define v (cdr (assoc "y" langs-alist string-ci=?))) > (vector-set! v 0 (cons 2010 (vector-ref v 0))) vector-set!: contract violation expected: (and/c vector? (not/c immutable?)) given: '#((2012 2011) ("Y is statically typed." "Something about Y."))
The problem is that the vector literal syntax, #(expr expr expr ...)
creates an immutable vector! Let's
confirm that:
> (immutable? v) #tAt this point I'm starting to lose the will to live but...if I put that S-expression into a file and then
(read ...)
it, I get mutable vectors. Observe:
% cat a8/alist.txt (("X" . #((2005) ("A poorly named language."))) ("Y" . #((2012 2011) ("Y is statically typed." "Something about Y.")))) % racket > (define a (read (open-input-file "a8/alist.txt"))) > (define v (cdr (assoc "y" a string-ci=?))) > (vector-set! v 0 (cons 2010 (vector-ref v 0))) > a '(("X" . #((2005) ("A poorly named language."))) ("Y" . #((2010 2012 2011) ("Y is statically typed." "Something about Y."))))
There's a long story behind this behavior but in short, it rises from differing "reader" behaviors when reading code
vs. data. I'll say that the Racket design decision of having #(...)
create
an immutable vector has produced a far-flung problem: an instructor teaching Racket (me!) has had to say quite a bit
about this behavior. He wonders how many students have found the will to hang on and read this far!
In Chez Scheme, #(...)
creates a mutable vector, although it does need to be quoted:
> (define v '#(1 2)) > (vector-set! v 0 'x) > v #(x 2)
Once again I find myself wondering if maybe I should have taught Scheme using Chez Scheme instead of teaching Racket, although Racket certainly seems to be the high ground in the Scheme community.
optab.rkt
One way to learn about a language's types and operators is to manually create tables that show what type
results from applying a binary operator to various pairs of types.
For this problem you are to write a Racket program, optab.rkt
, that generates such tables for Java, Python, and Haskell.
Here's a run of optab
using rk/i
:
% rk/i optab.rkt Welcome to Racket v8.5 [cs]. > (optab python * ISL) * | I S L ---+--------- I | I S L S | S * * L | L * *Here's what
optab
's three arguments mean:
The first argument, python
, specifies Python as the language of interest for this run.
The second argument, *
, specifies the operator of interest.
The third argument, ISL
, specifies the types of interest. The letters I
, S
, and L
stand
for int
, str
, and list
, respectively.
optab
's output is a table showing the type that results from applying the *
operator
to various pairs of types in Python. The row headings on the left specify the type of the left-hand operand.
The column headings along the top specify the type of the right-hand operand.
Here are some notes on interpreting the table shown above:
I
, shows that int * int
produces an int
.
L
,
shows that list * int
produces a list
.
S
in the middle of the top row shows that
int * str
produces a str
.
The *
's indicate that str * str
, str * list
, list * str
,
and list * list
are not permitted (i.e., have no meaning) in Python.
Here's an example with Java:
> (optab java * IFDCS) * | I F D C S ---+--------------- I | I F D I * F | F F D F * D | D D D D * C | I F D I * S | * * * * *
I
, F
, D
, C
, and S
stand for
int
, float
, double
, char
, and String
, respectively.
Here's how optab
is intended to work:
For the specified operator and types, try each pairwise combination of types with the given operator by executing that expression in the specified language and seeing what type is produced, or if an error is produced. Present the results in a table.
The table just above was produced by generating and then running each of twenty-five different Java programs and analyzing their output. Here's what the first Java program looked like:
% cat tryop.java public class tryop { public static void main(String args[]) { printClass(1 * 1); } private static void printClass(Object o) { System.out.println(o.getClass().getName()); } }Note the third line,
f(1 * 1);
That's an int
times an int
because
the first operation to test is I * I
.
The program relies on Java's "boxing" mechanism to convert the result of 1 * 1
into an object whose
type can be queried with getClass()
. You've seen this before, on Haskell slide 74.
Remember: A Racket program wrote that Java program, tryop.java
Let's run it by hand:
% java tryop.java java.lang.Integer %
That single line of output, java.lang.Integer
, tells us that in Java, multiplying an int
by
an int
produces an int
, which is "boxed" into an instance of Integer
.
Here's the tryop.java
that's generated for I * S
:
% cat tryop.java public class tryop { public static void main(String args[]) { printClass(1 * "abc"); } private static void printClass(Object o) { System.out.println(o.getClass().getName()); } }Note that it is identical to the
tryop.java
generated for I * I
with one exception:
the third line is different: instead of being 1 * 1
it's 1 * "abc"
.
Let's run it:
% java tryop.java tryop.java:3: error: bad operand types for binary operator '*' printClass(1 * "abc"); ^ first type: int second type: String 1 error error: compilation failed
The compilation error tells us that in Java, it's not valid to multiply an int
by a String
.
We need a way in Racket to run a command like java tryop.java
and capture its output. The procedure
run-command
, in a8/optab-starter.rkt
does that. Let's try it, first with the
tryop.java
that was generated for I * I
:
% cat tryop.java public class tryop { public static void main(String args[]) { printClass(1 * 1); } private static void printClass(Object o) { System.out.println(o.getClass().getName()); } } % rk/i a8/optab-starter.rkt Welcome to Racket v8.5 [cs]. > (run-command "/usr/bin/java" "tryop.java") '("java.lang.Integer\n" . "") >
Note that run-command
was called with two arguments: (1) The full path to the java
"executable",
/usr/bin/java
, and (2) the name of the Java source file to run, tryop.java
.
run-command
returns a dotted-pair. The car
is the data that was written to "standard output"
when /usr/bin/java" tryop.java
was run.
The cdr
is the data that was written to "error output". We see that "java.lang.Integer\n"
was
written to standard output, and that nothing was written to error output—the cdr
is an empty string.
Let's try it with the tryop.java
generated for I * S
:
% cat tryop.java public class tryop { public static void main(String args[]) { printClass(1 * "abc"); } private static void printClass(Object o) { System.out.println(o.getClass().getName()); } } % rk/i a8/optab-starter.rkt Welcome to Racket v8.5 [cs]. > (run-command "/usr/bin/java" "tryop.java") '("" . "tryop.java:3: error: bad operand types for binary operator '*'\n printClass(1 * \"abc\");\n ^\n first type: int\n second type: String\n1 error\nerror: compilation failed\n")
In this case, with an invalid combination of operands for *
in Java, there's no standard output but the error output
contains a multi-line message.
It appears that with Java, we can distinguish between valid cases (like I * I
)
and invalid cases (like I * S
) based on whether data was written to "standard output" or "error output".
Then, we can translate that success or failure into either a table entry like "I"
for an int
result,
or "*"
for an error.
Let's try Haskell with the /
operator. "D
" is for Double
.
> (optab haskell / IDS) / | I D S ---+--------- I | * * * D | * D * S | * * *For the first case,
I / I
, Racket generated this file, tryop.hs
:
% cat tryop.hs (1::Integer) / (1::Integer) :type itNote that just a plain
1
was good enough for Java since the literal 1
has the type int
but with Haskell we use (1::Integer)
to be sure the type is Integer
.
(Yes; Integer
, not Int
.)
Let's try running it. For Java we used (run-command "/usr/bin/java" "tryop.java")
but for Haskell
we need something more elaborate:
> (run-command "/bin/bash" "-c" "ghci -ignore-dot-ghci < tryop.hs") '("GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help\n Prelude> Prelude> Prelude> Prelude> Leaving GHCi.\n" . "\nOuch—an error! That's going to be a ":1:1: error:\n • No instance for (Fractional Integer) arising from a use of ‘/’\n • In the expression: (1 :: Integer) / (1 :: Integer)\n...lots more...")
*
".
Here's the tryop.hs
file generated for D * D
:
% cat tryop.hs (1.0::Double) * (1.0::Double) :type itLet's try it:
> (run-command "/bin/bash" "-c" "ghci -ignore-dot-ghci < tryop.hs") '("GHCi, version 8.6.5: http://www.haskell.org/ghc/ :? for help\nPrelude> Prelude> 1.0\nPrelude> it :: Double\nPrelude> Leaving GHCi.\n" . "")If we look closely at the output above, we see
it
, with a type: it :: Double
(underlined
above for emphasis).
Thus, for the case I / I
we need the table entry to be "*"
but for
D / D
we want "D"
.
In pseudo-code, here's what optab
needs to do:
For each pairwise combination of types specified in the call to optab
...
run-command
procedure. (Copy it from
a8/optab-starter.rkt
into your optab.rkt
.)
run-command
, determining either the type produced or that an error was produced.
type python3
will
show you where the python3
executable resides. (Do help type
to learn about the
type
command, which is a Bash built-in.)
I chose the names tryop.java
and tryop.hs
for the generated files but you can use any names you want.
Below is a listing of a8/mkfile.rkt
.
It is a complete program that generates a file named
hello.java
and runs it.
% cat a8/mkfile.rkt #lang racket ; ; `template` has the code for a Java class named hello. Each line ; of code is represented by a string, and those strings are in ; a list that is then joined into one string, with newlines between ; lines. ; ; Note that the println has two "~a" escapes. Further below we ; use `format` to interpolate a couple of values into the template ; (and that's why we call it `template`). ; ; Try printing it with (println template). ; (define template (string-join '( "public class hello {" " public static void main(String args[]) {" " System.out.println(~a * ~a);" " }" "}\n" ) "\n")) ; ; Open a file named "hello.java" for output, replacing it ; if it already exists. Assign the resulting "port" to p. ; (define p (open-output-file "hello.java" #:exists 'replace)) ; ; Use "format" to interpolate 1.2 and 'x' into `template`, putting ; the resulting into `program`. ; ; Try (println program) to see what gets built. Also try ; the commented define and see what it produces. ; (define program (format template 1.2 "'x'")) ;(define program (format template 3 "\"testing\"")) ; ; Write the program to the output file and close it. write-string ; returns the number of characters written to the file, and because ; this call is a top-level expression, the number of characters ; written (111) appears on standard output. (write-string program p) (close-output-port p) ; ; For simplicity, here's another copy of run-command. (define (run-command . args) (define-values (sp out in err) (apply subprocess #f #f #f args)) (let ([stdout (port->string out)] [stderr (port->string err)]) (close-input-port out) (close-output-port in) (close-input-port err) (subprocess-wait sp) (cons stdout stderr))) ; ; Run the just-created hello.java (run-command "/usr/bin/java" "hello.java")
% racket a8/mkfile.rkt 111 (number of characters written by write-string--see code above) '("144.0\n" . "") % ls -l hello.java -rw-rw-r-- 1 whm whm 111 Apr 20 22:32 hello.javaHere's the file that was created:
% cat hello.java public class hello { public static void main(String args[]) { System.out.println(1.2 * 'x'); } }Copy
a8/mkfile.rkt
into your a8
directory on lectura and run it, to help you get the idea of generating a program, running it, and then doing something with its output.
The following table shows what types must be supported in each language, and a good expression to use for testing with that type.
Letter | Haskell | Java | Python |
I | (1::Integer) | 1 | 1 |
F | (1.0::Float) | 1.0F | 1.0 |
D | (1.0::Double) | 1.0 | not supported |
B | True | true | True |
C | 'c' | 'c' | not supported |
S | "abc" | "abc" | "abc" |
O | not supported | new Object() | not supported |
L | not supported | not supported | [1] |
optab.rkt
optab.rkt
is not required to do any error checking at all. In particular, you can make these assumptions:
optab
's first argument is haskell
, java
, or python
.
optab
is a macro whose expansion calls optab-main
:
(define-syntax-rule (optab lang op types) (optab-main 'lang 'op 'types))
The macro "quotes" the arguments, making them symbols, so that we can type (optab java * IFDCS)
instead of (optab 'java '* 'IFDCS)
. Put the two-line optab
macro above
in your optab.rkt
.
(optab java * IFDCS)
takes almost 30 seconds to run on lectura. The same test for Haskell takes about seven seconds.
let-list.rkt
Write a macro let-list
that behaves like this:
> (let-list h t '(a b c) (printf "h: ~a, t: ~a\n" h t)) h: a, t: (b c) > (let-list x xs (range 1 5) (displayln x) (displayln xs)) 1 (2 3 4) > (let-list c cs (string->list "abcd") (list->string (list* c c cs))) "aabcd"Here's a description:
let-list
is assumed to be a list.
let-list
are variable names that
are to be bound to the car
and cdr
of that list, respectively.
let-list
are only visible in the enclosed expressions:
> (let-list x xs (range 1 5) (displayln x) (displayln xs)) 1 (2 3 4) > x x: undefined; ... > xs xs: undefined; ... > (define x 5) > (let-list x y (cons 3 4) (cons y x)) '(4 . 3) > x 5
j-for.rkt
We've had another letter from Cuthbert at Camp Racket. He found our +=
macro to be just what
he wanted! Now he's wondering if we can recreate something like Java's for
loop in Racket,
so let's do that.
Here's a Java for
-loop:
for (int i = 1; i <= 5; i += 1) { System.out.printf("i = %d\n", i); }Here's the corresponding
j-for
, assuming that we've put a copy of our +=
macro in
j-for.rkt
:
> (j-for (i 1) (<= i 5) (+= i 1) (printf "i = ~a\n" i)) i = 1 i = 2 i = 3 i = 4 i = 5
> (j-for (L '(a b c)) (pair? L) (set! L (cdr L)) (displayln (car L)) (displayln (cdr L)) (displayln "------")) a (b c) ------ b (c) ------ c () ------ > (j-for (vals (map odd? '(3 1 5 2 9))) (car vals) empty (displayln "odd") (set! vals (cdr vals))) odd odd oddAs a simplification,
j-for
's first argument must have the form (identifier expr)
.
We can't make an infinite loop like this,
(j-for empty (displayln "loop") empty)but we could do this,
(j-for (x 0) (displayln "loop") empty)or this,
(j-for (x 0) 0 (displayln "loop"))
In general, here's the syntax of j-for
:
(j-for (var init-expr ) test-expr advance-expr body-form1 ... body-formN)
As the infinite loop examples demonstrate, there may be zero body-form
s.
double.rkt
Write a macro double
that "doubles" the value of each of the variables it is given as parameters:
> (define x 3) > (define y 7) > (double x y) > x 6 > y 14The same variable may be specified multiple times:
> (define i 1) > (double i i i i i) > i 32 > (double i) > i 64 > (let ([a 1/1024]) (double a a a a a a) a) 1/16
Along with doubling a number, double
also "doubles" strings, lists, and vectors:
> s "testing" > lst '(a b c d) > v '#(10 20 30) > (double s lst v) > s "testingtesting" > lst '(a b c d a b c d) > v '#(10 20 30 10 20 30) > (define s "|") > (double s s s s s s) > s "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"Let's combine
double
with our j-for
:
> (j-for (x 1/3) (< x 3) (double x) (displayln x)) 1/3 2/3 4/3 8/3
Along with performing the side effects of "doubling" the given variables, double
returns #<void>
.
> (println (double x y)) #<void>
If double is given no arguments, it has no effect.
> (double) >
The behavior of double
is undefined if called with anything other than variables
having numbers, strings, lists, or vectors for values.
optab-extra.txt
For three points of extra credit per language, have your optab.rkt
support up to three additional languages of your choice.
PHP, Ruby, and Standard ML come to mind as easy possibilities since they're all installed on lectura.
but it's also fine to support a language that's only installed on your machine.
Details:
optab-extra.txt
, that shows your extended version in action.
macro-extra.txt
For up to three points of extra credit, devise and implement a macro of your own design. Something as simple
as let-list
would be worth a point. Something as complicated as double
would likely be worth three points.
Be sure that your creation is something that needs to be a macro.
For example, something like (add 3 4)
that produces 7
could be implemented as a macro
but an ordinary procedure would suffice. In contrast, there's no way to implement things
like +=
, show
, and while
in the slides without using
a macro—they're special forms.
Submit a plain text file macro-extra.txt
, that shows your macro in action with at
least two different examples.
The burden of proof for this extra credit is on you, not me!
observations.txt
Submit a plain text file named observations.txt
with...
(a) (1 point extra credit) An estimate of how many hours it took you to complete this assignment. Put that estimate on a line by itself, like this:
Hours: 9.5There should be only one
"Hours:"
line in observations.txt
. (It's fine if you care to provide per-problem times, and that data is useful to us, but report it in some form of your own invention that doesn't contain the string "Hours:"
. Thanks!)
Feedback and comments about the assignment are welcome, too. Was it too long, too hard, too detailed? Speak up! I appreciate all feedback, favorable or not.
(b) (1-3 points extra credit) Cite an interesting course-related observation (or observations) that you made while working on the assignment. The observation should have at least a little bit of depth. Think of me thinking "Good!" as one point, "Excellent!" as two points, and "Wow!" as three points. I'm looking for quality, not quantity.
Use a8/turnin
to submit your work.
Here's what I see for my solutions at the moment, using a8/rksize
,
which counts the number of left parentheses, square brackets, and curly braces—
which I claim is a pretty good proxy
for the "size" of a body of Racket code.
% a8/rksize $(grep -v txt a8/delivs) uniq.rkt: 31 showfacts.rkt: 132 optab.rkt: 217 let-list.rkt: 11 j-for.rkt: 19 double.rkt: 27
Point values of problems correspond closely to the "assignment points" mentioned in the syllabus. For example, a 10-point problem would correspond to about 1% of your final grade in the course.
Feel free to use comments as you see fit, but no comments are required in your code.
Remember that late assignments are not accepted and that there are no late days; but if circumstances beyond your control interfere with your work on this assignment, there may be grounds for an extension. See the syllabus for details.
My estimate is that it will take a typical CS junior from 8 to 10 hours to complete this assignment, assuming they successfully completed assignment 7.
Our goal is that everybody gets 100% on this assignment AND gets it done in an amount of time that is reasonable for them.
Assignments are not take-home tests! We hope you'll make use of Piazza, email, Discord and office hours if problems arise. If you're getting toward the hours I estimate above and don't seem to be close to completing it, or you're simply worried about your progress, email us! Give us a chance to speed you up!