2013-01-11 - Introduction to Racket - Quick Introduction to Racket - Lists & Recursion - Some Style ======================================================================== >>> Introduction to Racket * General layout of the parts of Racket: - The Racket language is (mostly) in the Scheme family, or more generally in the Lisp family; - Racket: the core language implementation (language and runtime), written mostly in C; - The actual language(s) that are available in Racket have lots of additional parts that are implemented in Racket itself; - GRacket: a portable Racket GUI extension, written in Racket too; - DrRacket: a GRacket application (also written in Racket); - Our language(s)... * Documentation: the Racket documentation is your friend (But beware that some things are provided in different forms from different places) ======================================================================== Side-note: E.W. DIJKSTRA "Goto Statement Considered Harmful." This paper tries to convince us that the well-known goto statement should be eliminated from our programming languages or, at least (since I don't think that it will ever be eliminated), that programmers should not use it. It is not clear what should replace it. The paper doesn't explain to us what would be the use of the "if" statement without a "goto" to redirect the flow of execution: Should all our postconditions consist of a single statement, or should we only use the arithmetic "if," which doesn't contain the offensive "goto"? And how will one deal with the case in which, having reached the end of an alternative, the program needs to continue the execution somewhere else? The author is a proponent of the so-called "structured programming" style, in which, if I get it right, gotos are replaced by indentation. Structured programming is a nice academic exercise, which works well for small examples, but I doubt that any real-world program will ever be written in such a style. More than 10 years of industrial experience with Fortran have proved conclusively to everybody concerned that, in the real world, the goto is useful and necessary: its presence might cause some inconveniences in debugging, but it is a de facto standard and we must live with it. It will take more than the academic elucubrations of a purist to remove it from our languages. Publishing this would waste valuable paper: Should it be published, I am as sure it will go uncited and unnoticed as I am confident that, 30 years from now, the goto will still be alive and well and used as widely as it is today. Confidential comments to the editor: The author should withdraw the paper and submit it someplace where it will not be peer reviewed. A letter to the editor would be a perfect choice: Nobody will notice it there! ======================================================================== >>> Quick Introduction to Racket Racket syntax... Similar to other Sexpr-based languages. Reminder: the parens can be compared to C/etc function cal parens -- they always mean that some function is applied. This is the reason why (+ (1) (2)) won't work: if you use C syntax that is "+(1(), 2())" but "1" isn't a function so "1()" is an error. An important difference between _syntax_ and _semantics_. A good way to think about this is the difference between the string "42" stored in a file somewhere (two ASCII values), and the number "42" stored in memory (in some representation). You could also continue with the above example: there is nothing wrong with "murder" -- it's just a word, but murder is something you'll go to jail for. The evaluation function that Racket uses is actually a function that takes a piece of syntax and returns (or executes) its semantics. ======================================================================== `define' expressions are used for creating new bindings, do not try to use them to change values. For example, you should not try to write something like (define x (+ x 1)) in an attempt to mimic `x = x+1'. It will not work. ======================================================================== There are two boolean values built in to Racket: `#t' (true) and `#f' (false). They can be used in `if' statements, for example: (if (< 2 3) 10 20) --> 10 because (< 2 3) evaluates to `#t'. As a matter of fact, *any* value except for `#f' is considered to be true, so: (if 0 1 2) --> 1 (if "false" 1 2) --> 1 (if "" 1 2) --> 1 (if null 1 2) --> 1 (if #f 1 2) --> 2 ; the only false value ======================================================================== Note: Racket is a _functional_ language -- so _everything_ has a value. This makes the expression (if test consequent) have no meaning when "test" evaluates to `#f'. This is unlike Pascal/C where statements _do_ something (side effect) like printing or an assignment -- here an if statement with no alternate part will just "do nothing" if the test is false... Racket, however, must return some value -- it could decide on simply returning `#f' (or some unspecified value) as the value of (if #f something) as some implementations do, but Racket just declares it a syntax error. (As we will see in the future, Racket has a more convenient `when' with a clearer intention.) ======================================================================== Well, *almost* everything is a value... There are certain things that are part of Racket's syntax -- for example `if' and `define' are special forms, they do not have a value! More about this shortly. (Bottom line: much more things do have a value, compared with other languages.) ======================================================================== `cond' is used for a sequence of `if...else if...else if...else'. The problem is that nested `if's are inconvenient. For example, (define (digit-num n) (if (<= n 9) 1 (if (<= n 99) 2 (if (<= n 999) 3 (if (<= n 9999) 4 "a lot"))))) In C/Java/Whatever, you'd write: function digit_num(n) { if (n <= 9) return 1; else if (n <= 99) return 2; else if (n <= 999) return 3; else if (n <= 9999) return 4; else return "a lot"; } (Side question: why isn't there a `return' statement in Racket?) But trying to force Racket code to look similar: (define (digit-num n) (if (<= n 9) 1 (if (<= n 99) 2 (if (<= n 999) 3 (if (<= n 9999) 4 "a lot"))))) is more than just bad taste -- the indentation rules are there for a reason, the main one is that you can see the structure of your program at a quick glance, and this is no longer true in the above code. (Such code will be penalized!) So, instead of this, we can use Racket's `cond' statement, like this: (define (digit-num n) (cond [(<= n 9) 1] [(<= n 99) 2] [(<= n 999) 3] [(<= n 9999) 4] [else "a lot"])) Note that `else' is a keyword that is used by the `cond' form -- you should always use an `else' clause (for similar reasons as an `if', to avoid an extra expression evaluation there, and we will need it when we use a typed language). Also note that square brackets are read by DrRacket like round parens, it will only make sure that the paren pairs match. We use this to make code more readable -- specifically, there is a major difference between the above use of "[]" from the conventional use of "()". Can you see what it is? The general structure of a `cond': (cond [test-1 expr-1] [test-2 expr-2] ... [test-n expr-n] [else else-expr]) ======================================================================== Example for using an if statement, and a recursive function: (define (fact n) (if (zero? n) 1 (* n (fact (- n 1))))) Use this to show the different tools, esp: * special objects that *cannot* be used * syntax-checker * stepper * submission tool (installing, registering and submitting) An example of converting it to tail recursive form: (define (helper n acc) (if (zero? n) acc (helper (- n 1) (* acc n)))) (define (fact n) (helper n 1)) ======================================================================== Additional notes about homework submissions: * Begin every function with clear documentation: a purpose of statement and its type. * Document the function when needed, and according to the guidelines above and in the style guide. * After the function, always have a few test cases -- they should cover your complete code (make sure to include possible corner cases). Later on, we will switch to testing the whole file through it's "public interface", instead of testing each function. ======================================================================== >>> Lists & Recursion Lists are a fundamental Racket data type. A list is defined as either: 1. the empty list (`null', `empty', or '()), 2. a pair (`cons' cell) of anything and a list. As simple as this may be, it gives us precise *formal* rules to prove that something is a list. * Why is there a "the" in the first rule? Examples: null (cons 1 null) (cons 1 (cons 2 (cons 3 null))) (list 1 2 3) ; a more convenient function to get the above List operations -- predicates: null? ; true only for the empty list pair? ; true for any cons cell list? ; this can be defined using the above We can derive `list?' from the above rules: (define (list? x) (if (null? x) #t (and (pair? x) (list? (rest x))))) (define (list? x) (or (null? x) (and (pair? x) (list? (rest x))))) But why can't we define `list?' more simply as (define (list? x) (or (null? x) (pair? x))) The difference between the above definition and the proper one can be observed in the full Racket language, not in the student languages (where there are no pairs with non-list values in their tails). List operations -- destructors for pairs (cons cells): first rest Traditionally called `car', `cdr'. Also, any `cr' combination for that is made of up to four `a's and/or `d's -- we will probably not use much more than `cadr', `caddr' etc. ======================================================================== Example for recursive function involving lists: (define (list-length list) (if (null? list) 0 (+ 1 (list-length (rest list))))) Use different tools, esp: * syntax-checker * stepper How come we could use `list' as an argument -- use the syntax checker (define (list-length-helper list len) (if (null? list) len (list-length-helper (rest list) (+ len 1)))) (define (list-length list) (list-length-helper list 0)) Main idea: lists are a recursive structure, so functions that operate on lists should be recursive functions that follow the recursive definition of lists. Another example for list function -- summing a list of numbers (define (sum-list l) (if (null? l) 0 (+ (first l) (sum-list (rest l))))) Also show how to implement `rcons', using this guideline. ======================================================================== More examples: Define `reverse' -- solve the problem using `rcons'. `rcons' can be generalized into something very useful: `append'. * How would we use `append' instead of `rcons'? * How much time will this take? Does it matter if we use `append' or `rcons'? Redefine `reverse' using tail recursion. * Is the result more complex? (Yes, but not too bad because it collects the elements in reverse.) ======================================================================== >>> Some Style When you have some common value that you need to use in several places, it is bad to duplicate it. For example: (define (how-many a b c) (cond [(> (* b b) (* 4 a c)) 2] [(= (* b b) (* 4 a c)) 1] [(< (* b b) (* 4 a c)) 0])) What's bad about it? * It's longer than necessary, which will eventually make your code less readable. * It's slower -- by the time you reach the last case, you have evaluated the two sequences three times. * It's more prone to bugs -- the above code is short enough, but what if it was longer so you don't see the three occurrences on the same page? Will you remember to fix all places when you debug the code months after it was written? In general, the ability to use names is probably the most fundamental concept in computer science -- the fact that makes computer programs what they are. We already have a facility to name values: function arguments. We could split the above function into two like this: (define (how-many-helper b^2 4ac) ; note the identifier name! (cond [(> b^2 4ac) 2] [(= b^2 4ac) 1] [else 0])) (define (how-many a b c) (how-many-helper (* b b) (* 4 a c))) But instead of the awkward solution of coming up with a new function just for its names, we have a facility to bind local names -- `let'. In general, the syntax for a `let' special form is (let ([id expr] ...) expr) For example, (let ([x 1] [y 2]) (+ x y)) But note that the bindings are done "in parallel", for example, try this: (let ([x 1] [y 2]) (let ([x y] [y x]) (list x y))) Using this for the above problem: (define (how-many a b c) (let ([b^2 (* b b)] [4ac (* 4 a c)]) (cond [(> b^2 4ac) 2] [(= b^2 4ac) 1] [else 0]))) ======================================================================== Some notes on writing code (also see the style-guide in the handouts section) *** Code quality will be graded to in this course! * Use abstractions whenever possible, as said above. This is bad: (define (how-many a b c) (cond ((> (* b b) (* 4 a c)) 2) ((= (* b b) (* 4 a c)) 1) ((< (* b b) (* 4 a c)) 0))) (define (what-kind a b c) (cond ((= a 0) 'degenerate) ((> (* b b) (* 4 a c)) 'two) ((= (* b b) (* 4 a c)) 'one) ((< (* b b) (* 4 a c)) 'none))) * But don't over abstract: (define one 1) (define two "two") * Always do test cases (show coverage tool), you might want to comment them, but you should always make sure your code works. * Do not under-document, but also don't over-document. * INDENTATION! (Let DrRacket decide for you, and get used to its rules) --> This is part of the culture that was mentioned last time, but it's done this way for good reason: decades of programming experience have shown this to be the most readable format. * As a general rule, `if' should be either all on one line, or the condition on the first and each consequent on a separate line. Similarly for `define' -- either all on one line or a newline after the object that is being define (either an identifier or a an identifier with arguments). * Another general rule: you should never have white space after an open-paren, or before a close paren (white space includes newlines). Also, before an open paren there should be either another open paren or white space, and the same goes for after a closing paren. * Use the tools that are available to you: for example, use `cond' instead of nested ifs (definitely do not force the indentation to make a nested `if' look like its C counterpart -- remember to let DrRacket indent for you). Another example -- do not use `(+ 1 (+ 2 3))' instead of `(+ 1 2 3)' (this might be needed in *extremely* rare situations, only when you know your calculus and have extensive knowledge about round-off errors). Another example -- do not use `(cons 1 (cons 2 (cons 3 null)))' instead of `(list 1 2 3)'. Also -- don't write things like: (if (< x 100) #t #f) since it's the same as just (< x 100) A few more of these: (if x #t y) --same-as--> (or x y) (if x y #f) --same-as--> (and x y) (if x #f #t) --same-as--> (not x) (Actually the first two are almost the same, for example, (and 1 2) will return 2, not `#t'.) * Use these as examples for many of these issues: (define (interest x) (* x (cond [(and (> x 0) (<= x 1000)) 0.04] [(and (> x 1000) (<= x 5000)) 0.045] [else 0.05]))) (define (how-many a b c) (cond ((> (* b b) (* (* 4 a) c)) 2) ((< (* b b) (* (* 4 a) c)) 0) (else 1))) (define (what-kind a b c) (if (equal? a 0) 'degenerate (if (equal? (how-many a b c) 0) 'zero (if (equal? (how-many a b c) 1) 'one 'two) ) ) ) (define (interest deposit) (cond [(< deposit 0) "invalid deposit"] [(and (>= deposit 0) (<= deposit 1000)) (* deposit 1.04) ] [(and (> deposit 1000) (<= deposit 5000)) (* deposit 1.045)] [(> deposit 5000) (* deposit 1.05)])) (define (interest deposit) (if (< deposit 1001) (* 0.04 deposit) (if (< deposit 5001) (* 0.045 deposit) (* 0.05 deposit)))) (define (what-kind a b c) (cond ((= 0 a) 'degenerate) (else (cond ((> (* b b)(*(* 4 a) c)) 'two) (else (cond ((= (* b b)(*(* 4 a) c)) 'one) (else 'none))))))); ========================================================================