;; 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 racket/contract) (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) |# ; A natch is: ; interp: natural number (spelled weirdly, to be clear we're NOT meaning the built-in ints.) ; - 'sunya ; interp: zero (Btw: "sunya" is sanskrit & hindi for "zero") ; - (make-succ [natch]) ; interp: The successor of some other(smaller) natch. (define-struct succ (pred)) ; make-succ : natch -> succ ; examples of data: 'sunya (make-succ 'sunya) ; interpretation: one (the successor of zero) (make-succ (make-succ 'sunya)) ; interpretation: two (the successor of one) (make-succ (make-succ (make-succ 'sunya))) ; interpretation: three ; It's kinda annoying to have to call all those constructors just to talk about three; ; let's give some names: (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 cero 'sunya) ; for completeness ; is-zero? : any -> boolean ; Is `v` our representation of zero as a `natch`? (define (is-zero? v) (and (symbol? v) (symbol=? v 'sunya))) ; Btw, if you want to define the type "natch?" something racket's contract-system can use, then: (define (natch? x) (or (is-zero? x) (and (succ? x) (natch? (succ-pred x))))) ; Template: ; func-for-natch : natch -> ?? (define (func-for-natch a-nat) (cond [(is-zero? a-nat) ...] [(succ? a-nat) (... (func-for-natch (succ-pred a-nat)))])) (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") ; string* : natch, string -> string ; Return a string which is `n` copies of `word`. ; (define (string* n word) (cond [(is-zero? n) ""] [(succ? n) (string-append word (string* (succ-pred n) word))])) (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) ; add : natch, natch -> natch ; return the sum of `m` and `n`. ; (define (add m n) (cond [(is-zero? m) n] [(succ? m) (make-succ (add (succ-pred m) n))])) #| 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. Of course, in real-life, we'll just start with this revised names, and use our language's ints / natural-numbers: |# ; countdown : nat-num -> string ; count down `n` seconds to launch. ; (define (countdown n) (cond [(zero? n) "blastoff!"] [(positive? n) (string-append (number->string n) ".. " (countdown (sub1 n)))])) (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!") ; boxes-bullseye : natch -> image ; return a picture of `n` concentric squares with decreasing sizes. (define (boxes-bullseye n) (cond [(zero? n) empty-image] [(positive? n) (overlay (square (* n 10) 'outline 'blue) (boxes-bullseye (sub1 n)))])) (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))) ; 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' -- 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! |#