;; The first three lines of this file were inserted by DrRacket. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-intermediate-lambda-reader.ss" "lang")((modname nats-theory) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) (require "student-extras.rkt") (require 2htdp/image) #| How would you represent natural-numbers, if they weren't built-in to your language? (if all you had were the notions of structs, union-types("or"), and symbols(meaningless bit-patterns)? Similar question: How does a mathematician *define* what the natural numbers are from scratch -- without just saying 'you know: the natural numbers; zero,one,two "and so on"'? Here's the answer mathematicians have devised -- but as runnable code. Btw, here are some videos, discussing the content in this file: https://youtu.be/lzxzYRC_a04 (up through the template) https://youtu.be/x7vuZXwvCyM (the functions using the template, and built-in numbers) |# ; To-model: natural numbers, which are either 0 or some other natural-number plus 1. ; datatype definition: ; (define (natch? val) ; interpretation: natural number (spelled weirdly, to be clear we're NOT meaning the built-in ints.) (or (equal? val 'sunya) ; interpretation: zero (Btw: "sunya" is sanskrit & hindi for "zero") (succ? val))) ; interpretation: The successor of some other(smaller) natch. (define-struct/c succ ([prev natch?])) ; examples of data: 'sunya (make-succ 'sunya) (make-succ (make-succ 'sunya)) (make-succ (make-succ (make-succ 'sunya))) ;;; TODO ; interpretation: zero ; interpretation: one (the successor of zero) ; interpretation: two (the successor of one) ; interpretation: three ; It's kinda annoying to have to call all those constructors just to talk about three; ; let's give some names: ;;; TODO: uno, dos (define cero 'sunya) ; for completeness (define uno (make-succ 'sunya)) (define dos (make-succ uno)) (define tres (make-succ dos)) (define cuatro (make-succ tres)) (define cinco (make-succ cuatro)) (define seis (make-succ cinco)) (define siete (make-succ seis)) (define ocho (make-succ siete)) (define nueve (make-succ ocho)) (define dies (make-succ nueve)) (define/c (func-for-natch n) (-> natch? ...?) (cond [(equal? n 'sunya) ...] [(succ? n) (... (func-for-natch (succ-prev n)))])) (define/c (odd? n) (-> natch? boolean?) (cond [(equal? n 'sunya) #f] [(succ? n) (not (odd? (succ-prev n)))])) (check-expect (odd? cero) #f) (check-expect (odd? uno) #t) (check-expect (odd? dos) #f) (odd? ocho) ; Template: ; #;(define/contract (func-for-natch n) (-> natch? ...?) (cond [(equal? n 'sunya) ...] [(succ? n) (... (func-for-natch (succ-prev n)))])) (define/contract (string* n word) (-> natch? string? string?) ; Return a string which is `n` copies of `word`. ; (cond [(equal? n 'sunya) ""] [(succ? n) (string-append word (string* (succ-prev n) word))])) (check-expect (string* cero "") "") (check-expect (string* 'sunya "") "") (check-expect (string* uno "") "") (check-expect (string* (make-succ 'sunya) "") "") (check-expect (string* dos "") "") (check-expect (string* (make-succ (make-succ 'sunya)) "") "") (check-expect (string* cuatro "") "") (check-expect (string* cero "meow") "") (check-expect (string* 'sunya "meow") "") (check-expect (string* uno "meow") "meow") (check-expect (string* (make-succ 'sunya) "meow") "meow") (check-expect (string* dos "meow") "meowmeow") (check-expect (string* (make-succ (make-succ 'sunya)) "meow") "meowmeow") (check-expect (string* cuatro "meow") "meowmeowmeowmeow") (check-expect (string* (make-succ (make-succ (make-succ 'sunya))) "meow") "meowmeowmeow") (check-expect (string* (make-succ (make-succ (make-succ (make-succ 'sunya)))) "meow") "meowmeowmeowmeow") (define/contract (add n m) (-> natch? natch? natch?) (cond [(equal? n 'sunya) m] [(succ? n) (make-succ (add (succ-prev n) m))])) (check-expect (add cero cero) cero) (check-expect (add cero uno) uno) (check-expect (add uno cero) uno) (check-expect (add uno uno) dos) (check-expect (add uno dos) tres) (check-expect (add dos uno) tres) (check-expect (add tres cinco) ocho) #| You might think: "What a silly, and un-usable definition". It is indeed an academic/theoretical basis of what the natural numbers are(*). BUT: it also is a solid basis of real-world programs for (built-in) natural-numbers, which follow that natural, recursive data-definition. We just re-name the "constructor" and "selector" as adding 1 and subtracting one, with a base-case/sentinel value `0`, and we still think of `4` as simply a shorthand for `(add1 (add1 (add1 (add1 0))))`. In fact, the name `zero?` is a built-in racket function. We can simply re-name the above: (define make-succ add1) (define succ-pred sub1) (define is-zero? zero?) (define succ? positive?) After writing the above (and getting rid of the define-structs), our previous functions `string*` and `add` still work, but now using the *built-in* natural-numbers. (Fwiw: you can think of our `succ` as using unary-numerals; the built-in hardware uses arabic-numerals-base-two (list-of-bits), which is a cleverer datatype-definition which is exponentially more efficient (both for space, and for arithmetic (grade-school alg's for arithmetic using arabic numerals)). [The hardware also has faster arithmetic because it further in-lines "list-of-64-bits".]) Of course, in real-life, we'll just start with these revised names, and use our language's ints / natural-numbers: |# ; countdown : nat-num -> string ; count down `n` seconds to launch. ; (define/contract (countdown n) (-> natural? string?) (cond [(zero? n) "blastoff!"] [(positive? n) (string-append (number->string n) ".. " (countdown (- n 1)))])) (check-expect (countdown 0) "blastoff!") (check-expect (countdown 1) "1.. blastoff!") (check-expect (countdown 2) "2.. 1.. blastoff!") (check-expect (countdown 9) "9.. 8.. 7.. 6.. 5.. 4.. 3.. 2.. 1.. blastoff!") (define/contract (boxes-bullseye n) (-> natural? image?) ; return a picture of `n` concentric squares with decreasing sizes. ; (cond [(zero? n) empty-image] [(positive? n) (overlay (square (* n 10) 'outline 'blue) (boxes-bullseye (- n 1)))])) (check-expect (boxes-bullseye 0) empty-image) (check-expect (boxes-bullseye 1) (square 10 'outline 'blue)) (check-expect (boxes-bullseye 2) (overlay (square 20 'outline 'blue) (square 10 'outline 'blue))) (boxes-bullseye 75) ; Up-shot: the design-recipe applies to (looping over) natural-numbers, ; once we realize that natural-numbers are *actually* a union-type ; (either a zero-value, or a `succ` struct with 1 field). #| (*) If you think it's kinda cool that you can define natural-numbers from scratch, I suggest taking a 'foundations of math' course (MATH 300) to see how to extend these notions to define the integers, rationals, reals, complex numbers, arrays, quaternions, ...? Alternately, just try it for yourself: building on 'natch', define the type 'intz' (pos. or neg.) -- and get addition/multiplication working for it! Then 'rashionals' (a struct with two intz) and get arithmetic for them, and then even 'flowting-point" (a struct with an exponent and mantissa) -- ALL w/o using the built-in numbers! |#