diff options
Diffstat (limited to 'test/scanners/scheme/pleac.in.scm')
-rw-r--r-- | test/scanners/scheme/pleac.in.scm | 5141 |
1 files changed, 0 insertions, 5141 deletions
diff --git a/test/scanners/scheme/pleac.in.scm b/test/scanners/scheme/pleac.in.scm deleted file mode 100644 index 7c8c4a5..0000000 --- a/test/scanners/scheme/pleac.in.scm +++ /dev/null @@ -1,5141 +0,0 @@ -;;; -*- scheme -*- - -;;; @@PLEAC@@_NAME -;;; @@SKIP@@ Guile 1.8 - -;;; @@PLEAC@@_WEB -;;; @@SKIP@@ http://www.gnu.org/software/guile/ - -;;; @@PLEAC@@_INTRO -;;; @@SKIP@@ Sections 1 - 3, and 7 - 9, largely completed using Guile 1.5; subsequent additions use Guile 1.8. - -;;; @@PLEAC@@_APPENDIX -;;; @@SKIP@@ General-purpose, custom functions that might be used in several sections, appear here - -;; Helper which aims to reduce code clutter by: -;; * Replacing the oft-used, '(display item) (newline)' combination -;; * Avoiding overuse of '(string-append)' for simple output tasks -(define (print item . rest) - (let ((all-item (cons item rest))) - (for-each - (lambda (item) (display item) (display " ")) - all-item)) - (newline)) - -;; ------------ - -;; Slightly modified version of '(qx)' from Chapter 4 -(use-modules (ice-9 popen) (srfi srfi-1) (srfi srfi-13)) - -(define (drain-output port) - (let loop ((chars '()) - (next (read-char port))) - (if (eof-object? next) - ; Modified to not return last 'line' with newline - (list->string (reverse! (cdr chars))) - (loop (cons next chars) - (read-char port))))) - -(define (qx pipeline) - (let* ((pipe (open-input-pipe pipeline)) - (output (drain-output pipe))) - (close-pipe pipe) - output)) - -;; ------------ - -;; @@PLEAC@@_1.0 -(define string "\\n") ; two characters, \ and an n -(define string "\n") ; a "newline" character -(define string "Jon \"Maddog\" Orwant") ; literal double quotes -(define string "Jon 'Maddog' Orwant") ; literal single quotes - -(define a "This is a multiline here document -terminated by a closing double quote") - -;; @@PLEAC@@_1.1 -;; Use substring - -(substring str start end) -(substring str start) - -;; You can fill portions of a string with another string - -(substring-move-right! str start end newstring newstart) -(substring-move-left! str start end newstring newstart) - -;; Guile has a separate character type, and you can treat strings as a -;; character array. - -(string-ref str pos) -(string-set! str pos char) -(string-fill! str char) -(substring-fill! str start end char) - -(define s "This is what you have") -(define first (substring s 0 1)) ; "T" -(define start (substring s 5 7)) ; "is" -(define rest (substring s 13)) ; "you have" -(define last (substring s (1- (string-length s)))) ; "e" -(define end (substring s (- (string-length s) 4))) ; "have" -(define piece (let ((len (string-length s))) - (substring s (- len 8) (- len 5)))) ; "you" - - -;;; Or use the string library SRFI-13 -(use-modules (srfi srfi-13)) - -(define s "This is what you have") -(define first (string-take s 1)) ; "T" -(define start (xsubstring s 5 7)) ; "is" -(define rest (xsubstring s 13 -1)) ; "you have" -(define last (string-take-right s 1)) ; "e" -(define end (string-take-right s 4)) ; "have" -(define piece (xsubstring s -8 -5)) ; "you" - -;; Mutation of different sized strings is not allowed. You have to -;; use set! to change the variable. - -(set! s (string-replace s "wasn't" 5 7)) -;; This wasn't what you have -(set! s (string-replace s "ondrous" 13 25)) -;; This wasn't wondrous -(set! s (string-take-right s (1- (string-length s)))) -;; his wasn't wondrous -(set! s (string-take s 9)) - -;; @@PLEAC@@_1.2 -(define a (or b c)) -(define a (if (defined? b) b c)) -(define a (or (and (defined? b) b) c)) - -;; @@PLEAC@@_1.3 -;; This doesn't really make sense in Scheme... temporary variables are -;; a natural construct and cheap. If you want to swap variables in a -;; block without introducing any new variable names, you can use let: - -(let ((a b) (b a)) - ;; ... - ) - -(let ((alpha beta) (beta production) (production alpha)) - ;; ... - ) - -;; @@PLEAC@@_1.4 -(define num (char->integer char)) -(define char (integer->char num)) - -(use-modules (srfi srfi-13)) -(let ((str "sample")) - (display (string-join - (map number->string - (map char->integer (string->list str))) " ")) - (newline)) - -(let ((lst '(115 97 109 112 108 101))) - (display (list->string (map integer->char lst))) - (newline)) - -(letrec ((next (lambda (c) (integer->char (1+ (char->integer c)))))) - (let* ((hal "HAL") - (ibm (list->string (map next (string->list hal))))) - (display ibm) - (newline))) - -;; @@PLEAC@@_1.5 -;; Convert the string to a list of characters -(map proc - (string->list str)) - -(use-modules (srfi srfi-1)) -(format #t "unique chars are: ~A\n" - (apply string (sort (delete-duplicates - (string->list "an apple a day")) char<?))) - -(let* ((str "an apple a day") - (sum (apply + (map char->integer (string->list str))))) - (format #t "sum is ~A\n" sum)) - -;;; or use string-fold/string-map/string-for-each from SRFI-13 -(use-modules (srfi srfi-13)) - -(let* ((str "an apple a day") - (sum (string-fold (lambda (c acc) (+ acc (char->integer c))) - 0 str))) - (format #t "sum is ~A\n" sum)) - -#!/usr/local/bin/guile -s -!# -;; sum - compute 16-bit checksum of all input files -(use-modules (srfi srfi-13)) -(define (checksum p) - (let loop ((line (read-line p 'concat)) (sum 0)) - (if (eof-object? line) - (format #t "~A ~A\n" sum (port-filename p)) - (let ((line-sum (string-fold (lambda (c acc) - (+ acc (char->integer c))) - 0 line))) - (loop (read-line p 'concat) (modulo (+ sum line-sum) - (1- (expt 2 16)))))))) -(let ((args (cdr (command-line)))) - (if (null? args) - (checksum (current-input-port)) - (for-each (lambda (f) (call-with-input-file f checksum)) args))) - -#!/usr/local/bin/guile -s -!# -;; slowcat - emulate a s l o w line printer -(use-modules (ice-9 regex) (srfi srfi-2) (srfi srfi-13)) -(define args (cdr (command-line))) -(define delay 1) -(and-let* ((p (pair? args)) - (m (string-match "^-([0-9]+)$" (car args)))) - (set! delay (string->number (match:substring m 1))) - (set! args (cdr args))) -(define (slowcat p) - (let loop ((line (read-line p 'concat))) - (cond ((not (eof-object? line)) - (string-for-each - (lambda (c) (display c) (usleep (* 5 delay))) line) - (loop (read-line p 'concat)))))) -(if (null? args) - (slowcat (current-input-port)) - (for-each (lambda (f) (call-with-input-file f slowcat)) args)) - -;; @@PLEAC@@_1.6 -(define revbytes (list->string (reverse (string->list str)))) - -;;; Or from SRFI-13 -(use-modules (srfi srfi-13)) -(define revbytes (string-reverse str)) -(string-reverse! str) ; modifies in place - -(define revwords (string-join (reverse (string-tokenize str)) " ")) - -(with-input-from-file "/usr/share/dict/words" - (lambda () - (do ((word (read-line) (read-line))) - ((eof-object? word)) - (if (and (> (string-length word) 5) - (string=? word (string-reverse word))) - (write-line word))))) - -;; A little too verbose on the command line -;; guile --use-srfi=13 -c '(with-input-from-file "/usr/share/dict/words" (lambda () (do ((word (read-line) (read-line))) ((eof-object? word)) (if (and (> (string-length word) 5) (string=? word (string-reverse word))) (write-line word)))))' - -;; @@PLEAC@@_1.7 -;; Use regexp-substitute/global -(regexp-substitute/global - #f "([^\t]*)(\t+)" str - (lambda (m) - (let* ((pre-string (match:substring m 1)) - (pre-len (string-length pre-string)) - (match-len (- (match:end m 2) (match:start m 2)))) - (string-append - pre-string - (make-string - (- (* match-len 8) - (modulo pre-len 8)) - #\space)))) - 'post) - -;; @@PLEAC@@_1.8 -;; just interpolate $abc in strings: -(define (varsubst str) - (regexp-substitute/global #f "\\$(\\w+)" str - 'pre (lambda (m) (eval (string->symbol (match:substring m 1)) - (current-module))) - 'post)) - -;; interpolate $abc with error messages: -(define (safe-varsubst str) - (regexp-substitute/global #f "\\$(\\w+)" str - 'pre (lambda (m) - (catch #t - (lambda () (eval (string->symbol (match:substring m 1)) - (current-module))) - (lambda args - (format #f "[NO VARIABLE: ~A]" (match:substring m 1))))) - 'post)) - -;; interpolate ${(any (scheme expression))} in strings: -(define (interpolate str) - (regexp-substitute/global #f "\\${([^{}]+)}" str - 'pre (lambda (m) (eval-string (match:substring m 1))) 'post)) - -;; @@PLEAC@@_1.9 -(use-modules (srfi srfi-13)) - -(string-upcase "bo beep") ; BO PEEP -(string-downcase "JOHN") ; john -(string-titlecase "bo") ; Bo -(string-titlecase "JOHN") ; John - -(string-titlecase "thIS is a loNG liNE") ; This Is A Long Line - -#!/usr/local/bin/guile -s -!# -;; randcap: filter to randomly capitalize 20% of the time -(use-modules (srfi srfi-13)) -(seed->random-state (current-time)) -(define (randcap p) - (let loop ((line (read-line p 'concat))) - (cond ((not (eof-object? line)) - (display (string-map (lambda (c) - (if (= (random 5) 0) - (char-upcase c) - (char-downcase c))) - line)) - (loop (read-line p 'concat)))))) -(let ((args (cdr (command-line)))) - (if (null? args) - (randcap (current-input-port)) - (for-each (lambda (f) (call-with-input-file f randcap)) args))) - -;; @@PLEAC@@_1.10 -;; You can do this with format. Lisp/Scheme format is a little -;; different from what you may be used to with C/Perl style printf -;; (actually far more powerful) , but if you keep in mind that we use -;; ~ instead of %, and , instead of . for the prefix characters, you -;; won't have trouble getting used to Guile's format. - -(format #f "I have ~A guanacos." n) - -;; @@PLEAC@@_1.11 -(define var " - your text - goes here") - -(use-modules (ice-9 regexp)) -(set! var (regexp-substitute/global #f "\n +" var 'pre "\n" 'post)) - -(use-modules (srfi srfi-13)) -(set! var (string-join (map string-trim (string-tokenize var #\newline)) "\n")) - -(use-modules (ice-9 regexp) (srfi srfi-13) (srfi srfi-14)) -(define (dequote str) - (let* ((str (if (char=? (string-ref str 0) #\newline) - (substring str 1) str)) - (lines (string-tokenize str #\newline)) - (rx (let loop ((leader (car lines)) (lst (cdr lines))) - (cond ((string= leader "") - (let ((pos (or (string-skip (car lines) - char-set:whitespace) 0))) - (make-regexp (format #f "^[ \\t]{1,~A}" pos) - regexp/newline))) - ((null? lst) - (make-regexp (string-append "^[ \\t]*" - (regexp-quote leader)) - regexp/newline)) - (else - (let ((pos (or (string-prefix-length leader (car lst)) 0))) - (loop (substring leader 0 pos) (cdr lst)))))))) - (regexp-substitute/global #f rx str 'pre 'post))) - -;; @@PLEAC@@_1.12 -(use-modules (srfi srfi-13)) - -(define text "Folding and splicing is the work of an editor, -not a mere collection of silicon -and -mobile electrons!") - -(define (wrap str max-col) - (let* ((words (string-tokenize str)) - (all '()) - (first (car words)) - (col (string-length first)) - (line (list first))) - (for-each - (lambda (x) - (let* ((len (string-length x)) - (new-col (+ col len 1))) - (cond ((> new-col max-col) - (set! all (cons (string-join (reverse! line) " ") all)) - (set! line (list x)) - (set! col len)) - (else - (set! line (cons x line)) - (set! col new-col))))) - (cdr words)) - (set! all (cons (string-join (reverse! line) " ") all)) - (string-join (reverse! all) "\n"))) - -(display (wrap text 20)) - -;; @@PLEAC@@_1.13 -(define str "Mom said, \"Don't do that.\"") -(set! str (regexp-substitute/global #f "['\"]" str 'pre "\\" - match:substring 'post)) -(set! str (regexp-substitute/global #f "[^A-Z]" str 'pre "\\" - match:substring 'post)) -(set! str (string-append "this " (regexp-substitute/global - #f "\W" "is a test!" 'pre "\\" - match:substring 'post))) - -;; @@PLEAC@@_1.14 -(use-modules (srfi srfi-13)) - -(define str " space ") -(string-trim str) ; "space " -(string-trim-right str) ; " space" -(string-trim-both str) ; "space" - -;; @@PLEAC@@_1.15 -(use-modules (srfi srfi-2) (srfi srfi-13) (ice-9 format)) - -(define parse-csv - (let* ((csv-match (string-join '("\"([^\"\\\\]*(\\\\.[^\"\\\\]*)*)\",?" - "([^,]+),?" - ",") - "|")) - (csv-rx (make-regexp csv-match))) - (lambda (text) - (let ((start 0) - (result '())) - (let loop ((start 0)) - (and-let* ((m (regexp-exec csv-rx text start))) - (set! result (cons (or (match:substring m 1) - (match:substring m 3)) - result)) - (loop (match:end m)))) - (reverse result))))) - -(define line "XYZZY,\"\",\"O'Reilly, Inc\",\"Wall, Larry\",\"a \\\"glug\\\" bit,\",5,\"Error, Core Dumped\"") - -(do ((i 0 (1+ i)) - (fields (parse-csv line) (cdr fields))) - ((null? fields)) - (format #t "~D : ~A\n" i (car fields))) - -;; @@PLEAC@@_1.16 -(use-modules (srfi srfi-13) (srfi srfi-14)) - -;; Knuth's soundex algorithm from The Art of Computer Programming, Vol 3 -(define soundex - (letrec ((chars "AEIOUYBFPVCGJKQSXZDTLMNR") - (nums "000000111122222222334556") - (skipchars (string->char-set "HW")) - (trans (lambda (c) - (let ((i (string-index chars c))) - (if i (string-ref nums i) c))))) - (lambda (str) - (let* ((ustr (string-upcase str)) - (f (string-ref ustr 0)) - (skip (trans f))) - (let* ((mstr (string-map trans (string-delete ustr skipchars 1))) - (dstr (string-map (lambda (c) - (cond ((eq? c skip) #\0) - (else (set! skip c) c))) - mstr)) - (zstr (string-delete dstr #\0))) - (substring (string-append (make-string 1 f) zstr "000") 0 4)))))) - -(soundex "Knuth") ; K530 -(soundex "Kant") ; K530 -(soundex "Lloyd") ; L300 -(soundex "Ladd") ; L300 - -;; @@PLEAC@@_1.17 -#!/usr/local/bin/guile -s -!# - -(use-modules (srfi srfi-13) - (srfi srfi-14) - (ice-9 rw) - (ice-9 regex)) - -(define data "analysed => analyzed -built-in => builtin -chastized => chastised -commandline => command-line -de-allocate => deallocate -dropin => drop-in -hardcode => hard-code -meta-data => metadata -multicharacter => multi-character -multiway => multi-way -non-empty => nonempty -non-profit => nonprofit -non-trappable => nontrappable -pre-define => predefine -preextend => pre-extend -re-compiling => recompiling -reenter => re-enter -turnkey => turn-key") - -(define input (if (null? (cdr (command-line))) - (current-input-port) - (open-input-file (cadr (command-line))))) - -(let* ((newline-char-set (string->char-set "\n")) - (assoc-char-set (string->char-set " =>")) - (dict (map - (lambda (line) - (string-tokenize line assoc-char-set)) - (string-tokenize data newline-char-set))) - (dict-match (string-join (map car dict) "|"))) - (let loop ((line (read-line input))) - (cond ((not (eof-object? line)) - (regexp-substitute/global - (current-output-port) dict-match line - 'pre - (lambda (x) - (cadr (assoc (match:substring x 0) dict))) - 'post) - (loop (read-line input 'concat)))))) - -(close-port input) - -;; @@PLEAC@@_2.1 -;; Strings and numbers are separate data types in Scheme, so this -;; isn't as important as it is in Perl. More often you would use the -;; type predicates, string? and number?. - -(if (string-match "[^\\d]" str) (display "has nondigits")) -(or (string-match "^\\d+$" str) (display "not a natural number")) -(or (string-match "^-?\\d+$" str) (display "not an integer")) -(or (string-match "^[\\-+]?\\d+$" str) (display "not an integer")) -(or (string-match "^-?\\d+\.?\d*$" str) (display "not a decimal number")) -(or (string-match "^-?(\d+(\.\d*)?|\.\d+)$" str) - (display "not a decimal number")) -(or (string-match "^([+-]?)(\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$" str) - (display "not a C float")) - -(define num1 (string->number str)) - -(define num2 (read)) - -;; @@PLEAC@@_2.2 -;; (approx-equal? num1 num2 accuracy) : returns #t if num1 and num2 are -;; equal to accuracy number of decimal places -(define (approx-equal? num1 num2 accuracy) - (< (abs (- num1 num2)) (expt 10.0 (- accuracy)))) - -(define wage 536) ;; $5.36/hour -(define week (* 40 wage)) ;; $214.40 -(format #t "One week's wage is: $~$\n" (/ week 100.0)) - -;; @@PLEAC@@_2.3 -(round num) ;; rounds to inexact whole number -(inexact->exact num) ;; rounds to exact integer - -;; You can also use format to convert numbers to more precisely -;; formatted strings. Note Guile has a builtin format which is a more -;; limited version of that found in the (ice-9 format) module, to save -;; load time. Basically, if you are doing anything you couldn't do -;; with a series of (display), (write) and (newline), then you'll need -;; to use the module. -(use-modules (ice-9 format)) - -(define a 0.255) -(define b (/ (round (* 100.0 a)) 100.0)) -(format #t "Unrounded: ~F\nRounded: ~F\n" a b) -(format #t "Unrounded: ~F\nRounded: ~,2F\n" a a) - -(define a '(3.3 3.5 3.7 -3.3)) -(display "number\tint\tfloor\tceil\n") -(for-each - (lambda (n) - (format #t "~,1F\t~,1F\t~,1F\t~,1F\n" - n (round n) (floor n) (ceiling n))) - a) - -;; @@PLEAC@@_2.4 -;; numbers are radix independent internally, so you usually only -;; convert on output, however to convert strings: -(define (dec->bin num) - (number->string (string->number num 10) 2)) - -(define (bin->dec num) - (number->string (string->number num 2) 10)) - -(define num (bin->dec "0110110")) ; 54 -(define binstr (dec->bin "54")) ; 110110 - -;; @@PLEAC@@_2.5 -;; do is the most general loop iterator -(do ((i x (1+ i))) ; var init-value step-value - ((> i y)) ; end when true - ;; i is set to every integer from x to y, inclusive - ;; ... - ) - -;; Guile also offers a while loop -(let ((i x)) - (while (<= i y) - ;; i is set to every integer from x to y, inclusive - ; ... - (set! i (1+ i)))) - -;; named let is another common loop -(let loop ((i x)) - (cond ((<= i y) - ;; i is set to every integer from x to y, step-size 7 - ;; ... - (loop (+ i 7))))) ; tail-recursive call - -(display "Infancy is: ") -(do ((i 0 (1+ i))) - ((> i 2)) - (format #t "~A " i)) -(newline) - -(display "Toddling is: ") -(let ((i 3)) - (while (<= i 4) - (format #t "~A " i) - (set! i (1+ i)))) -(newline) - -(display "Childhood is: ") -(let loop ((i 5)) - (cond ((<= i 12) - (format #t "~A " i) - (loop (1+ i))))) -(newline) - -;; @@PLEAC@@_2.6 -;; format can output roman numerals - use ~:@R - -(use-modules (ice-9 format)) - -(format #t "Roman for ~R is ~:@R\n" 15 15) - -;; @@PLEAC@@_2.7 -(random 5) ; an integer from 0 to 4 -(random 5.0) ; an inexact real in the range [0,5) - -;; char sets from SRFI-14 and string-unfold from SRFI-13 make a quick -;; way to generate passwords - -(use-modules (srfi srfi-13) (srfi srfi-14)) - -(define chars (char-set->string char-set:graphic)) -(define size (char-set-size char-set:graphic)) -(define password - (string-unfold (lambda (x) (= x 8)) - (lambda (x) (string-ref chars (random size))) - 1+ 0)) - -;; @@PLEAC@@_2.8 -;; if you're working with random numbers you'll probably want to set -;; the random seed - -(seed->random-state (current-time)) - -;; you can also save random states and pass them to any of the above -;; random functions - -(define state (copy-random-state)) -(random:uniform) -;; 0.939377327721761 -(random:uniform state) -;; 0.939377327721761 - -;; @@PLEAC@@_2.9 -;; @@INCOMPLETE@@ -;; very inefficient -(use-modules (ice-9 rw)) -(define make-true-random - (letrec ((bufsize 8) - (accum (lambda (c acc) (+ (* 256 acc) - (char->integer c)))) - (getbuf (lambda () - (call-with-input-file "/dev/urandom" - (lambda (p) - (let ((buf (make-string bufsize))) - (read-string!/partial buf p) - buf)))))) - (lambda (rand-proc) - (lambda args - (let ((state (seed->random-state (string-fold accum 0 (getbuf))))) - (apply rand-proc (append args (list state)))))))) - -(define urandom (make-true-random random)) -(define urandom:exp (make-true-random random:exp)) -(define urandom:normal (make-true-random random:normal)) -(define urandom:uniform (make-true-random random:uniform)) - -;; @@PLEAC@@_2.10 -;; Guile offers a number of random distributions - -(random:exp) ; an inexact real in an exponential dist with mean 1 -(random:normal) ; an inexact real in a standard normal distribution -(random:uniform) ; a uniformly distributed inexact real in [0,1) - -;; There are also functions to fill vectors with random distributions - -;; Fills vector v with inexact real random numbers the sum of whose -;; squares is equal to 1.0. -(random:hollow-sphere! v) - -;; Fills vector v with inexact real random numbers that are -;; independent and standard normally distributed (i.e., with mean 0 -;; and variance 1). -(random:normal-vector! v) - -;; Fills vector v with inexact real random numbers the sum of whose -;; squares is less than 1.0. -(random:solid-sphere! v) - -;; @@PLEAC@@_2.11 -;; Guile's trigonometric functions use radians. - -(define pi 3.14159265358979) - -(define (degrees->radians deg) - (* pi (/ deg 180.0))) - -(define (radians->degrees rad) - (* 180.0 (/ rad pi))) - -(define (degree-sine deg) - (sin (degrees->radians deg))) - -;; @@PLEAC@@_2.12 - -;; Guile provides the following standard trigonometric functions (and -;; their hyperbolic equivalents), defined for all real and complex -;; numbers: - -(sin z) -(cos z) -(tan z) -(asin z) -(acos z) -(atan z) - -(acos 3.7) ; 0.0+1.9826969446812i - -;; @@PLEAC@@_2.13 -;; Guile provides log in base e and 10 natively, defined for any real -;; or complex numbers: - -(log z) ; natural logarithm -(log10 z) ; base-10 logarithm - -;; For other bases, divide by the log of the base: - -(define (log-base n z) - (/ (log z) (log n))) - -;; To avoid re-computing (log n) for a base you want to use -;; frequently, you can create a custom log function: - -(define (make-log-base n) - (let ((divisor (log n))) - (lambda (z) (/ (log z) divisor)))) - -(define log2 (make-log-base 2)) - -(log2 1024) - -;; @@PLEAC@@_2.14 -;; In addition to simple vectors, Guile has builtin support for -;; uniform arrays of an arbitrary dimension. - -;; a rows x cols integer matrix -(define a (make-array 0 rows cols)) -(array-set! a 3 row col) -(array-ref a row col) - -;; a 3D matrix of reals -(define b (make-array 0.0 x y z)) - -;; a literal boolean truth table for logical and -'#2((#f #f) (#f #t)) - -;; simple matrix multiplication - -(define (matrix-mult m1 m2) - (let* ((d1 (array-dimensions m1)) - (d2 (array-dimensions m2)) - (m1rows (car d1)) - (m1cols (cadr d1)) - (m2rows (car d2)) - (m2cols (cadr d2))) - (if (not (= m1cols m2rows)) - (error 'index-error "matrices don't match")) - (let ((result (make-array 0 m1rows m2cols))) - (do ((i 0 (1+ i))) - ((= i m1rows)) - (do ((j 0 (1+ j))) - ((= j m2cols)) - (do ((k 0 (1+ k))) - ((= k m1cols)) - (array-set! result (+ (array-ref result i j) - (* (array-ref m1 i k) - (array-ref m2 k j))) - i j)))) - result))) - -(matrix-mult '#2((3 2 3) (5 9 8)) '#2((4 7) (9 3) (8 1))) - -;; @@PLEAC@@_2.15 -;; Guile has builtin support for complex numbers: - -(define i 0+1i) ; 0.0+1.0i -(define i (sqrt -1)) ; 0.0+1.0i - -(complex? i) ; #t -(real-part i) ; 0.0 -(imag-part i) ; 1.0 - -(* 3+5i 2-2i) ; 16+4i -(sqrt 3+4i) ; 2+i - -;; Classic identity: -e^(pi*i) => 1 -(inexact->exact (real-part (- (exp (* pi 0+1i))))) ; 1 - -;; @@PLEAC@@_2.16 -;; You can type in literal numbers in alternate radixes: - -#b01101101 ; 109 in binary -#o155 ; 109 in octal -#d109 ; 109 in decimal -#x6d ; 109 in hexadecimal - -;; number->string and string->number also take an optional radix: - -(define number (string->number hexadecimal 16)) -(define number (string->number octal 8)) - -;; format will also output in different radixes: - -(format #t "~B ~O ~D ~X\n" num num num num) - -;; converting Unix file permissions read from stdin: - -(let loop ((perm (read-line))) - (cond ((not (eof-object? perm)) - (format #t "The decimal value is ~D\n" (string->number perm 8)) - (loop (read-line))))) - -;; @@PLEAC@@_2.17 -;; once again, format is our friend :) -(use-modules (ice-9 format)) - -;; the : prefix to the D directive causes commas to be output every -;; three digits. -(format #t "~:D\n" (random 10000000000000000)) -; => 2,301,267,079,619,540 - -;; the third prefix arg to the D directive is the separator character -;; to use instead of a comma, useful for European style numbers: -(format #t "~,,'.:D\n" (random 10000000000000000)) -; => 6.486.470.447.356.534 - -;; the F directive, however, does not support grouping by commas. to -;; achieve this, we can format the integer and fractional parts -;; separately: -(define (commify num) - (let ((int (inexact->exact (truncate num)))) - (if (= num int) - (format #f "~:D" int) - (string-append (format #f "~:D" int) - (let ((str (format #f "~F" num))) - (substring str (or (string-index str #\.) - (string-length str)))))))) - -;; @@PLEAC@@_2.18 -;; format can handle simple 's' plurals with ~p, and 'y/ies' plurals -;; with the @ prefix: - -(format #t "It took ~D hour~P\n" hours hours) - -(format #t "It took ~D centur~@P\n" centuries centuries) - -(define noun-plural - (let* ((suffixes '(("ss" . "sses") - ("ph" . "phes") - ("sh" . "shes") - ("ch" . "ches") - ("z" . "zes") - ("ff" . "ffs") - ("f" . "ves") - ("ey" . "eys") - ("y" . "ies") - ("ix" . "ices") - ("s" . "ses") - ("x" . "xes") - ("ius" . "ii"))) - (suffix-match - (string-append "(" (string-join (map car suffixes) "|") ")$")) - (suffix-rx (make-regexp suffix-match))) - (lambda (noun) - (let ((m (regexp-exec suffix-rx noun))) - (if m - (string-append (regexp-substitute #f m 'pre) - (cdr (assoc (match:substring m) suffixes))) - (string-append noun "s")))))) - -;; @@PLEAC@@_2.19 -#!/usr/local/bin/guile -s -!# - -;; very naive factoring algorithm -(define (factor n) - (let ((factors '()) - (limit (inexact->exact (round (sqrt n)))) - (twos 0)) - ;; factor out 2's - (while (even? n) - (set! n (ash n -1)) - (set! twos (1+ twos))) - (if (> twos 0) (set! factors (list (cons 2 twos)))) - ;; factor out odd primes - (let loop ((i 3)) - (let ((r (remainder n i))) - (cond ((= r 0) - (set! n (quotient n i)) - (let* ((old-val (assv i factors)) - (new-val (if old-val (1+ (cdr old-val)) 1))) - (set! factors (assv-set! factors i new-val))) - (loop i)) - ((< i limit) - (loop (+ 2 i)))))) - ;; remainder - (if (> n 1) (set! factors (cons (cons n 1) factors))) - (reverse! factors))) - -;; pretty print a term of a factor -(define (pp-term pair) - (if (= (cdr pair) 1) - (number->string (car pair)) - (format #f "~A^~A" (car pair) (cdr pair)))) - -;; factor each number given on the command line -(for-each - (lambda (n) - (let ((factors (factor n))) - (format #t "~A = ~A" n (pp-term (car factors))) - (for-each - (lambda (x) (format #t " * ~A" (pp-term x))) - (cdr factors)) - (newline))) - (map string->number (cdr (command-line)))) - -;; @@PLEAC@@_3.0 -;; Use the builtin POSIX time functions - -;; get the current time -(current-time) ; number of seconds since the epoch -(gettimeofday) ; pair of seconds and microseconds since the epoch - -;; create a time object from an integer (e.g. returned by current-time) -(localtime time) ; in localtime -(gmtime time) ; in UTC - -;; get/set broken down components of a time object - -(tm:sec time) (set-tm:sec time secs) ; seconds (0-59) -(tm:min time) (set-tm:min time mins) ; minutes (0-59) -(tm:hour time) (set-tm:hour time hours) ; hours (0-23) -(tm:mday time) (set-tm:mday time mday) ; day of the month (1-31) -(tm:mon time) (set-tm:mon time month) ; month (0-11) -(tm:year time) (set-tm:year time year) ; year minus 1900 (70-) -(tm:wday time) (set-tm:wday time wday) ; day of the week (0-6) - ; where Sunday is 0 -(tm:yday time) (set-tm:yday time yday) ; day of year (0-365) -(tm:isdst time) (set-tm:isdst time isdst) ; daylight saving indicator - ; 0 for "no", > 0 for "yes", - ; < 0 for "unknown" -(tm:gmtoff time) (set-tm:gmtoff time off) ; time zone offset in seconds - ; west of UTC (-46800 to 43200) -(tm:zone time) (set-tm:zone time zone) ; Time zone label (a string), - ; not necessarily unique. - -(format #t "Today is day ~A of the current year.\n" - (tm:yday (localtime (current-time)))) - -;; Or use SRFI-19 - Time and Date Procedures -(use-modules (srfi srfi-19)) - -(define now (current-date)) ; immutable once created - -(date-nanosecond now) ; 0-9,999,999 -(date-second now) ; 0-60 (60 represents a leap second) -(date-minute now) ; 0-59 -(date-hour now) ; 0-23 -(date-day now) ; 0-31 -(date-month now) ; 1-12 -(date-year now) ; integer representing the year -(date-year-day now) ; day of year (Jan 1 is 1, etc.) -(date-week-day now) ; day of week (Sunday is 0, etc.) -(date-week-number now start) ; week of year, ignoring a first partial week - ; start is the first day of week as above -(date-zone-offset now) ; integer number of seconds east of GMT - -(format #t "Today is day ~A of the current year.\n" - (date-year-day (current-date))) - -;; @@PLEAC@@_3.1 -;; using format and POSIX time components -(use-modules (ice-9 format)) -(let ((now (localtime (current-time)))) - (format #t "The current date is ~4'0D ~2'0D ~2'0D\n" - (+ 1900 (tm:year now)) (tm:mon now) (tm:mday now))) - -;; using format and SRFI-19 time components -(use-modules (srfi srfi-19) (ice-9 format)) -(let ((now (current-date))) - (format #t "The current date is ~4'0d-~2'0D-~2'0D\n" - (date-year now) (date-month now) (date-day now))) - -;; using POSIX strftime with a libc time format string -(display (strftime "%Y-%m-%d\n" (localtime (current-time)))) - -;; @@PLEAC@@_3.2 -;; set the individual components of a time struct and use mktime -(define time (localtime (current-time))) -(set-tm:mday time mday) -(set-tm:mon time mon) -(set-tm:year time year) -(car (mktime time)) ; mktime returns a (epoch-seconds . time) pair - -;; or use SRFI-19's make-date and date->time-monotonic -(use-modules (srfi srfi-19)) -(date->time-monotonic - (make-date nanosecond second minute hour day month year zone-offset)) - -;; @@PLEAC@@_3.3 -;; use localtime or gmtime with the accessors mentioned in the -;; introduction to this chapter -(let ((time (localtime seconds))) ; or gmtime - (format #t "Dateline: ~2'0d:~2'0d:~2'0d-~4'0d/~2'0d/~2'0d\n" - (tm:hour time) (tm:min time) (tm:sec time) - (+ 1900 (tm:year time)) (1+ (tm:mon time)) (tm:mday time))) - -;; or use SRFI-19 -(use-modules (srfi srfi-19)) -(let* ((time (make-time time-monotonic nanosecond second))) - (display (date->string (time-monotonic->date time) "~T-~1\n"))) - -;; @@PLEAC@@_3.4 -;; just add or subtract epoch seconds -(define when (+ now difference)) -(define then (- now difference)) - -;; if you have DMYHMS values, you can convert them to times or add -;; them as seconds: -(define birthtime 96176750) -(define interval (+ 5 ; 5 seconds - (* 17 60) ; 17 minutes - (* 2 60 60) ; 2 hours - (* 55 60 60 24))) ; and 55 days -(define then (+ birthtime interval)) -(format #t "Then is ~A\n" (strftime "%a %b %d %T %Y" (localtime then))) - -;; @@PLEAC@@_3.5 -;; subtract the epoch seconds: -(define bree 361535725) -(define nat 96201950) -(define difference (- bree nat)) -(format #t "There were ~A seconds between Nat and Bree\n" difference) - -;; or use SRFI-19's time arithmetic procedures: -(use-modules (srfi srfi-19)) -(define time1 (make-time time-monotonic nano1 sec1)) -(define time2 (make-time time-monotonic nano2 sec2)) -(define duration (time-difference time1 time2)) -(time=? (subtract-duration time1 duration) time2) ; #t -(time=? (add-duration time2 duration) time1) ; #t - -;; @@PLEAC@@_3.6 -;; convert to a SRFI-19 date and use the accessors -(use-modules (srfi srfi-19)) -(date-day date) -(date-year-day date) -(date-week-day date) -(date-week-number date start-day-of-week) - -;; @@PLEAC@@_3.7 -;; use the strptime function: -(define time-pair (strptime "%Y-%m-%d" "1998-06-03")) -(format #t "Time is ~A\n." (strftime "%b %d, %Y" (car time-pair))) - -;; or use SRFI-19's string->date: -(use-modules (srfi srfi-19)) -(define date (string->date "1998-06-03" "~Y-~m-~d")) -(format #t "Time is ~A.\n" (date->string date)) - -;; @@PLEAC@@_3.8 -;; use the already seen strftime: -(format #t "strftime gives: ~A\n" - (strftime "%A %D" (localtime (current-time)))) - -;; or SRFI-19's date->string: -(use-modules (srfi srfi-19)) -(format #t "default date->string gives: ~A\n" (date->string (current-date))) -(format #t "date->string gives: ~A\n" - (date->string (current-date) "~a ~b ~e ~H:~M:~S ~z ~Y")) - -;; @@PLEAC@@_3.9 -;; gettimeofday will return seconds and microseconds: -(define t0 (gettimeofday)) -;; do your work here -(define t1 (gettimeofday)) -(format #t "You took ~A seconds and ~A microseconds\n" - (- (car t1) (car t0)) (- (cdr t1) (cdr t0))) - -;; you can also get more detailed info about the real and processor -;; times: -(define runtime (times)) -(tms:clock runtime) ; the current real time -(tms:utime runtime) ; the CPU time units used by the calling process -(tms:stime runtime) ; the CPU time units used by the system on behalf - ; of the calling process. -(tms:cutime runtime) ; the CPU time units used by terminated child - ; processes of the calling process, whose status - ; has been collected (e.g., using `waitpid'). -(tms:cstime runtime) ; the CPU times units used by the system on - ; behalf of terminated child processes - -;; you can also use the time module to time execution: -(use-modules (ice-9 time)) -(time (sleep 3)) -;; clock utime stime cutime cstime gctime -;; 3.01 0.00 0.00 0.00 0.00 0.00 -;; 0 - -;; @@PLEAC@@_3.10 -(sleep i) ; sleep for i seconds -(usleep i) ; sleep for i microseconds (not available on all platforms) - -;; @@PLEAC@@_4.0 -(define nested '("this" "that" "the" "other")) -(define nested '("this" "that" ("the" "other"))) -(define tune '("The" "Star-Spangled" "Banner")) - -;; @@PLEAC@@_4.1 -(define a '("quick" "brown" "fox")) -(define a '("Why" "are" "you" "teasing" "me?")) - -(use-modules (srfi srfi-13)) -(define lines - (map string-trim - (string-tokenize "\ - The boy stood on the burning deck, - It was as hot as glass." - #\newline))) - -(define bigarray - (with-input-from-file "mydatafile" - (lambda () - (let loop ((lines '()) - (next-line (read-line))) - (if (eof-object? next-line) - (reverse lines) - (loop (cons next-line lines) - (read-line))))))) - -(define banner "The Mines of Moria") - -(define name "Gandalf") -(define banner - (string-append "Speak, " name ", and enter!")) -(define banner - (format #f "Speak, ~A, and welcome!" name)) - -;; Advanced shell-like function is provided by guile-scsh, the Guile -;; port of SCSH, the Scheme shell. Here we roll our own using the -;; pipe primitives that come with core Guile. -(use-modules (ice-9 popen)) - -(define (drain-output port) - (let loop ((chars '()) - (next (read-char port))) - (if (eof-object? next) - (list->string (reverse! chars)) - (loop (cons next chars) - (read-char port))))) - -(define (qx pipeline) - (let* ((pipe (open-input-pipe pipeline)) - (output (drain-output pipe))) - (close-pipe pipe) - output)) - -(define his-host "www.perl.com") -(define host-info (qx (format #f "nslookup ~A" his-host))) - -(define perl-info (qx (format #f "ps ~A" (getpid)))) -(define shell-info (qx "ps $$")) - -(define banner '("Costs" "only" "$4.95")) -(define brax (map string (string->list "()<>{}[]"))) -(define rings (string-tokenize "Nenya Narya Vilya")) -(define tags (string-tokenize "LI TABLE TR TD A IMG H1 P")) -(define sample - (string-tokenize "The vertical bar (|) looks and behaves like a pipe.")) -(define ships '("Niña" "Pinta" "Santa MarÃa")) - -;; @@PLEAC@@_4.2 -(define array '("red" "yellow" "green")) - -(begin - (display "I have ") - (for-each display array) - (display " marbles.\n")) -;; I have redyellowgreen marbles. - -(begin - (display "I have ") - (for-each (lambda (colour) - (display colour) - (display " ")) - array) - (display "marbles.\n")) -;; I have red yellow green marbles. - -;; commify - insertion of commas into list output -(define (commify strings) - (let ((len (length strings))) - (case len - ((0) "") - ((1) (car strings)) - ((2) (string-append (car strings) " and " (cadr strings))) - ((3) (string-append (car strings) ", " - (cadr strings) ", and " - (caddr strings))) - (else - (string-append (car strings) ", " - (commify (cdr strings))))))) - -(define lists '(("just one thing") - ("Mutt" "Jeff") - ("Peter" "Paul" "Mary") - ("To our parents" "Mother Theresa" "God") - ("pastrami" "ham and cheese" "peanut butter and jelly" "tuna") - ("recycle tired, old phrases" "ponder big, happy thoughts") - ("recycle tired, old phrases" - "ponder big, happy thoughts" - "sleep and dream peacefully"))) - -(for-each (lambda (list) - (display "The list is: ") - (display (commify list)) - (display ".\n")) - lists) - -;; The list is: just one thing. -;; The list is: Mutt and Jeff. -;; The list is: Peter, Paul, and Mary. -;; The list is: To our parents, Mother Theresa, and God. -;; The list is: pastrami, ham and cheese, peanut butter and jelly, and tuna. -;; The list is: recycle tired, old phrases and ponder big, happy thoughts. -;; The list is: recycle tired, old phrases, ponder big, happy thoughts, and sleep and dream peacefully. - -;; @@PLEAC@@_4.3 -;;----------------------------- - -;; Scheme does not normally grow and shrink arrays in the way that -;; Perl can. The more usual operations are adding and removing from -;; the head of a list using the `cons' and `cdr' procedures. -;; However ... -(define (grow/shrink list new-size) - (let ((size (length list))) - (cond ((< size new-size) - (grow/shrink (cons "" list) new-size)) - ((> size new-size) - (grow/shrink (cdr list) new-size)) - (else list)))) - -(define (element list i) - (list-ref list (- (length list) i 1))) - -(define (set-element list i value) - (if (>= i (length list)) - (set! list (grow/shrink list (- i 1)))) - (set-car! (list-cdr-ref list (- (length list) i 1))) - list) - -(define (what-about list) - (let ((len (length list))) - (format #t "The array now has ~A elements.\n" len) - (format #t "The index of the last element is ~A.\n" (- len 1)) - (format #t "Element #3 is `~A'.\n" (if (> len 3) - (element list 3) - "")))) - -;; In the emulation of Perl arrays implemented here, the elements are -;; in reverse order when compared to normal Scheme lists. -(define people (reverse '("Crosby" "Stills" "Nash" "Young"))) -(what-about people) -;;----------------------------- -;; The array now has 4 elements. -;; The index of the last element is 3. -;; Element #3 is `Young'. -;;----------------------------- -(set! people (grow/shrink people 3)) -(what-about people) -;;----------------------------- -;; The array now has 3 elements. -;; The index of the last element is 2. -;; Element #3 is `'. -;;----------------------------- -(set! people (grow/shrink people 10001)) -(what-about people) -;;----------------------------- -;; The array now has 10001 elements. -;; The index of the last element is 10000. -;; Element #3 is `'. -;;----------------------------- - -;; @@PLEAC@@_4.4 -; Using a 'list' i.e. chain of pairs -(define *mylist* '(1 2 3)) - -; Apply procedure to each member of 'mylist' -(for-each - (lambda (item) (print item)) - *mylist*) - -;; ------------ - -; Using a 'vector' i.e. one-dimensional array -(define *bad-users* '#("lou" "mo" "sterling" "john")) - -(define (complain user) - (print "You're a *bad user*," user)) - -(array-for-each - (lambda (user) (complain user)) - *bad-users*) - -;; ------------ - -; Could probably get away with sorting a list of strings ... -(define *sorted-environ* - (sort (environ) string<?)) - -(for-each - (lambda (var) (display var) (newline)) - *sorted-environ*) - -;; ---- - -; ... but the intent here is to sort a hash table, so we'll use -; an 'assoc', Scheme's native dictionary type, which is really -; nothing more than a list of conses / dotted pairs [hash tables -; will be used in later examples] -(define (cons->env-string a) - (string-append (car a) "=" (cdr a))) - -(define (env-string->cons s) - (let ((key-value (string-split s #\=))) - (cons (car key-value) (cadr key-value)))) - -(define *sorted-environ-assoc* - (sort - (map - (lambda (var) (env-string->cons var)) - (environ)) - (lambda (left right) (string<? (car left) (car right))) )) - -(for-each - (lambda (var) - (print (car var) "=" (cdr var))) - *sorted-environ-assoc*) - -;; ---------------------------- - -(define *MAX-QUOTA* 100) - -(define (get-all-users) ...) -(define (get-usage user) ...) -(define (complain user) ...) - -(for-each - (lambda (user) - (let ((disk-usage (get-usage user))) - (if (> disk-usage *MAX-QUOTA*) - (complain user)))) - (get-all-users)) - -;; ---------------------------- - -(for-each - (lambda (user) (if (string=? user "tchrist") (print user))) - (string-split (qx "who|cut -d' ' -f1|uniq") #\newline)) - -;; ---------------------------- - -(use-modules (srfi srfi-13) (srfi srfi-14)) - -(do ((line (read-line) (read-line))) - ((eof-object? line)) - (for-each - (lambda (word) (print (string-reverse word))) - (string-tokenize line char-set:graphic))) - -;; ---------------------------- - -; Updates vector in-place [accepts variable number of vectors] -; See also the library function, 'array-map-in-order!' and its -; brethren -(define (vector-map-in-order! proc vec . rest) - (let ((all-vec (cons vec rest))) - (for-each - (lambda (vec) - (let ((end (vector-length vec))) - (let loop ((idx 0)) - (cond - ((= idx end) '()) - (else - (vector-set! vec idx (apply proc (list (vector-ref vec idx)))) - (loop (+ idx 1)))) ))) - all-vec))) - -;; ---- - -; A non-mutating version - illustration only, as library routines -; [SRFI-43 and built-ins] should be preferred -(define (vector-map-in-order proc vec . rest) - (let* ((all-vec (cons vec rest)) - (new-vec-len (reduce + 0 (map vector-length all-vec))) - (new-vec (make-vector new-vec-len)) - (new-vec-idx 0)) - (let loop ((all-vec all-vec)) - (cond - ((= new-vec-idx new-vec-len) new-vec) - (else - (array-for-each - (lambda (element) - (vector-set! new-vec new-vec-idx (apply proc (list element))) - (set! new-vec-idx (+ new-vec-idx 1))) - (car all-vec)) - (loop (cdr all-vec)) ))) )) - -;; ------------ - -(define *array* '#(1 2 3)) - -(array-for-each - (lambda (item) - (print "i =" item)) - *array*) - -;; ------------ - -(define *array* '#(1 2 3)) - -(array-for-each - (lambda (item) - (print "i =" item)) - *array*) - -; Since a 'vector' is mutable, in-place updates allowed -(vector-map-in-order! - (lambda (item) (- item 1)) - *array*) - -(print *array*) - -;; ------------ - -(define *a* '#(0.5 3)) -(define *b* '#(0 1)) - -(vector-map-in-order! - (lambda (item) (* item 7)) - *a* *b*) - -(print *a* *b*) - -;; ---------------------------- - -; Using 'for-each' to iterate over several container items is a -; simple matter of passing a list of those items e.g. a list of -; strings, or of arrays etc. -; -; However, complications arise when: -; * Heterogenous list of items e.g. list contains all of arrays, -; hashes, strings, etc. Necesitates different handling based on type -; * Item needs updating. It is not possible to alter the item reference -; and updating an item's internals is only possible if the relevant -; mutating procedures are implemented e.g. specified string characters -; may be altered in-place, but character deletion requires a new be -; created [i.e. altering the item reference], so is not possible - -(define *scalar* "123 ") -(define *array* '#(" 123 " "456 ")) -(define *hash* (list (cons "key1" "123 ") (cons "key2" " 456"))) - -; Illustrates iteration / handling of heterogenous types -(for-each - (lambda (item) - (cond - ((string? item) (do-stuff-with-string item)) - ((vector? item) (do-stuff-with-vector item)) - ((pair? item) (do-stuff-with-hash item)) - (else (print "unknown type")))) - (list *scalar* *array* *hash*)) - -; So, for item-replacement-based updating you need to use explicit -; iteration e.g. 'do' loop, or recursion [as is done in the code for -; 'vector-map-in-order!'] - examples in next section. Or, you could -; create a new 'for-each' type control structure using Scheme's -; macro facility [example not shown] - -;; @@PLEAC@@_4.5 -(define *array* '#(1 2 3)) - -;; ---- - -; Whilst a 'vector' is mutable, 'array-for-each' passes only a copy -; of each cell, thus there is no way to perform updates -(array-for-each - (lambda (item) - ... do some non-array-mutating task with 'item'...) - *array*) - -;; ------------ - -; For mutating operations, use one of the mutating 'array-map-...' routines -; or the custom, 'vector-map-in-order!' -(vector-map-in-order! - (lambda (item) - ... do some array-mutating task with 'item'...) - *array*) - -;; ------------ - -; Alternatively, use 'do' to iterate over the array and directly update -(let ((vector-length (vector-length *array*))) - (do ((i 0 (+ i 1))) - ((= i vector-length)) - ... do some array-mutating task with current array element ...)) - -;; ------------ - -; Alternatively, use a 'named let' to iterate over array and directly update -(let ((vector-length (vector-length *array*))) - (let loop ((i 0)) - (cond - ((= i vector-length) '()) - (else - ... do some array-mutating task with current array element ... - (loop (+ i 1)))) )) - -;; ---------------------------- - -(define *fruits* '#("Apple" "Blackberry")) - -;; ------------ - -(array-for-each - (lambda (fruit) - (print fruit "tastes good in a pie.")) - *fruits*) - -;; ------------ - -(let ((vector-length (vector-length *fruits*))) - (do ((i 0 (+ i 1))) - ((= i vector-length)) - (print (vector-ref *fruits* i) "tastes good in a pie.") )) - -;; ---------------------------- - -(define *rogue-cats* '("Blacky" "Ginger" "Puss")) - -(define *name-list* (acons 'felines *rogue-cats* '())) - -;; ------------ - -(for-each - (lambda (cat) - (print cat "purrs hypnotically..")) - (cdr (assoc 'felines *name-list*))) - -;; ------------ - -(let loop ((felines (cdr (assoc 'felines *name-list*)))) - (cond - ((null? felines) '()) - (else - (print (car felines) "purrs hypnotically..") - (loop (cdr felines))))) - -;; @@PLEAC@@_4.6 -(use-modules (srfi srfi-1)) - -; Simplest [read: least code] means of removing duplicates is to use -; SRFI-1's 'delete-duplicates' routine - -(define *non-uniq-num-list* '(1 2 3 1 2 3)) -(define *uniq* (delete-duplicates *my-non-uniq-num-list*) - -;; ------------ - -(use-modules (srfi srfi-1)) - -; Another simple alternative is to use SRFI-1's 'lset-union' routine. In -; general, the 'lset-...' routines: -; - convenient, but not fast; probably best avoided for 'large' sets -; - operate on standard lists, so simple matter of type-converting arrays and such -; - care needs to be taken in choosing the needed equality function - -(define *non-uniq-string-list* '("abc" "def" "ghi" "abc" "def" "ghi")) -(define *uniq* (lset-union string=? *non-uniq-string-list* *non-uniq-string-list*)) - -;; ---- - -(define *non-uniq-sym-list* '('a 'b 'c 'a 'b 'c)) -(define *uniq* (lset-union equal? *my-non-uniq-sym-list* *my-non-uniq-sym-list*)) - -;; ---- - -(define *non-uniq-num-list* '(1 2 3 1 2 3)) -(define *uniq* (lset-union = *my-non-uniq-num-list* *my-non-uniq-num-list*)) - -;; ---------------------------- - -;; Perl Cookbook-based examples - illustrative only, *not* recommended approaches - -(use-modules (srfi srfi-1)) - -(define *list* '(1 2 3 1 2 7 8 1 8 2 1 3)) -(define *seen* '()) - -; Use hash to filter out unique items -(for-each - (lambda (item) - (if (not (assoc-ref *seen* item)) - (set! *seen* (assoc-set! *seen* item #t)))) - *list*) - -; Generate list of unique items -(define *uniq* - (fold-right - (lambda (pair accum) (cons (car pair) accum)) - '() - *seen*)) - -;; ------------ - -(define *list* '(1 2 3 1 2 7 8 1 8 2 1 3)) -(define *seen* '()) - -; Build list of unique items by checking set membership -(for-each - (lambda (item) - (if (not (member item *seen*)) - (set! *seen* (cons item *seen*)))) - *list*) - -;; ------------ - -(define *users* - (sort - (string-split (qx "who|cut -d' ' -f1") #\newline) - string<?)) - -(define *seen* '()) - -; Build list of unique users by checking set membership -(for-each - (lambda (user) - (if (not (member user *seen*)) - (set! *seen* (cons item *seen*)))) - *list*) - -;; @@PLEAC@@_4.7 -; All problems in this section involve, at core, set difference -; operations. Thus, the most compact and straightforward approach is -; to utilise SRFI-1's 'lset-difference' routine - -(use-modules (srfi srfi-1)) - -(define *a* '(1 3 5 6 7 8)) -(define *b* '(2 3 5 7 9)) - -; *difference* contains elements in *a* but not in *b*: 1 6 8 -(define *difference* (lset-difference = *a* *b*)) - -; *difference* contains elements in *b* but not in *a*: 2 9 -(set! *difference* (lset-difference = *b* *a*)) - -;; ---------------------------- - -;; Perl Cookbook-based example - illustrative only, *not* recommended approaches - -(use-modules (srfi srfi-1)) - -(define *a* '(1 3 5 6 7 8)) -(define *b* '(2 3 5 7 9)) - -(define *a-only* '()) - -; Build list of items in *a* but not in *b* -(for-each - (lambda (item) - (if (not (member item *b*)) - (set! *a-only* (cons item *a-only*)))) - *a*) - -;; @@PLEAC@@_4.8 -; The SRFI-1 'lset-xxx' routines are appropriate here - -(use-modules (srfi srfi-1)) - -(define *a* '(1 3 5 6 7 8)) -(define *b* '(2 3 5 7 9)) - -; Combined elements of *a* and *b* sans duplicates: 1 2 3 5 6 7 8 9 -(define *union* (lset-union = *a* *b*)) - -; Elements common to both *a* and *b*: 3 5 7 -(define *intersection* (lset-intersection = *a* *b*)) - -; Elements in *a* but not in *b*: 1 6 8 -(define *difference* (lset-difference = *a* *b*)) - -;; ---------------------------- - -;; Perl Cookbook-based example - illustrative only, *not* recommended approaches - -(use-modules (srfi srfi-1)) - -(define *a* '(1 3 5 6 7 8)) -(define *b* '(2 3 5 7 9)) - -(define *union* '()) -(define *isect* '()) -(define *diff* '()) - -;; ------------ - -; Union and intersection -(for-each - (lambda (item) (set! *union* (assoc-set! *union* item #t))) - *a*) - -(for-each - (lambda (item) - (if (assoc-ref *union* item) - (set! *isect* (assoc-set! *isect* item #t))) - (set! *union* (assoc-set! *union* item #t))) - *b*) - -; Difference *a* and *b* -(for-each - (lambda (item) - (if (not (assoc-ref *isect* item)) - (set! *diff* (assoc-set! *diff* item #t)))) - *a*) - -(set! *union* - (fold - (lambda (pair accum) (cons (car pair) accum)) - '() - *union*)) - -(set! *isect* - (fold - (lambda (pair accum) (cons (car pair) accum)) - '() - *isect*)) - -(set! *diff* - (fold - (lambda (pair accum) (cons (car pair) accum)) - '() - *diff*)) - -(print "Union count: " (length *union*)) -(print "Intersection count:" (length *isect*)) -(print "Difference count: " (length *diff*)) - -;; @@PLEAC@@_4.9 -; Arrays, specifically vectors in the current context, are fixed-size -; entities; joining several such together requires copying of their -; contents into a new, appropriately-sized, array. This task may be -; performed: - -; * Directly: loop through existing arrays copying elements into a -; newly-created array - -(define (vector-join vec . rest) - (let* ((all-vec (cons vec rest)) - (new-vec-len (reduce + 0 (map vector-length all-vec))) - (new-vec (make-vector new-vec-len)) - (new-vec-idx 0)) - (let loop ((all-vec all-vec)) - (cond - ((= new-vec-idx new-vec-len) new-vec) - (else - (array-for-each - (lambda (element) - (vector-set! new-vec new-vec-idx element) - (set! new-vec-idx (+ new-vec-idx 1))) - (car all-vec)) - (loop (cdr all-vec)) ))) )) - -;; ---- - -(define *array1* '#(1 2 3)) -(define *array2* '#(4 5 6)) - -(define *newarray* - (vector-join *array1* *array2*)) - -;; ---------------------------- - -; * Indirectly; convert arrays to lists, append the lists, convert -; resulting list back into an array - -(define *array1* '#(1 2 3)) -(define *array2* '#(4 5 6)) - -(define *newarray* - (list->vector (append (vector->list *array1*) (vector->list *array2*)) )) - -; Of course if random access is not required, it is probably best to simply -; use lists since a wealth of list manipulation routines are available - -;; ---------------------------- - -; While Perl offers an all-purpose 'splice' routine, a cleaner approach is -; to separate out such functionality; here three routines are implemented -; together offering an equivalent to 'splice'. The routines are: -; * vector-replace! [use with 'vector-copy' to avoid changing original] -; e.g. (vector-replace! vec ...) -; (set! new-vec (vector-replace! (vector-copy vec) ...)) -; * vector-delete -; * vector-insert - -(define (vector-replace! vec pos item . rest) - (let* ((all-items (cons item rest)) - (pos (if (< pos 0) (+ (vector-length vec) pos) pos)) - (in-bounds - (not (> (+ pos (length all-items)) (vector-length vec))))) - (if in-bounds - (let loop ((i pos) (all-items all-items)) - (cond - ((null? all-items) vec) - (else - (vector-set! vec i (car all-items)) - (loop (+ i 1) (cdr all-items))) )) - ;else - vec))) - -(define (vector-delete vec pos len) - (let* ((new-vec-len (- (vector-length vec) len)) - (new-vec #f) - (pos (if (< pos 0) (+ (vector-length vec) pos) pos))) - (cond - ((< new-vec-len 0) vec) - (else - (set! new-vec (make-vector new-vec-len)) - (let loop ((vec-idx 0) (new-vec-idx 0)) - (cond - ((= new-vec-idx new-vec-len) new-vec) - (else - (if (= vec-idx pos) (set! vec-idx (+ vec-idx len))) - (vector-set! new-vec new-vec-idx (vector-ref vec vec-idx)) - (loop (+ vec-idx 1) (+ new-vec-idx 1)) ))) )) )) - -; This routine would probably benefit from having 'cmd' implemented as a keyword -; argument. However, 'cmd' implemented as a positional to keep example simple -(define (vector-insert vec pos cmd item . rest) - (let* ((all-item-vec (list->array 1 (cons item rest))) - (all-item-vec-len (vector-length all-item-vec)) - (vec-len (vector-length vec)) - (new-vec (make-vector (+ vec-len all-item-vec-len))) - (pos (if (< pos 0) (+ (vector-length vec) pos) pos))) - (if (eq? cmd 'after) (set! pos (+ pos 1))) - (vector-move-left! vec 0 pos new-vec 0) - (vector-move-left! all-item-vec 0 all-item-vec-len new-vec pos) - (vector-move-left! vec pos vec-len new-vec (+ pos all-item-vec-len)) - new-vec)) - -;; ---- - -(define *members* '#("Time" "Flies")) -(define *initiates* '#("An" "Arrow")) - -(set! *members* (vector-join *members* *initiates*)) - -;; ------------ - -(set! *members* (vector-insert *members* 1 'after "Like" *initiates*)) -(print *members*) - -(set! *members* (vector-replace *members* 0 "Fruit")) -(set! *members* (vector-replace *members* -2 "A" "Banana")) -(print *members*) - -; was: '#("Time" "Flies" "An" "Arrow") -; now: '#("Fruit" "Flies" "Like" "A" "Banana") - -;; @@PLEAC@@_4.10 -; As for appending arrays, there is the choice of iterating through -; the array: -(define (vector-reverse! vec) - (let loop ((i 0) (j (- (vector-length vec) 1))) - (cond - ((>= i j) vec) - (else - (vector-ref-swap! vec i j) - (loop (+ i 1) (- j 1)))) )) - -;; ------------ - -(define *array* '#(1 2 3)) - -(vector-reverse! *array*) - -;; ------------ - -(define *array* '#(1 2 3)) - -(do ((i (- (vector-length *array*) 1) (- i 1))) - ((< i 0)) - ... do something with *array* ...) - -;; ---------------------------- - -; or of converting to / from a list, performing any manipulation using -; the list routines - -(define *array* '#(1 2 3)) - -(define *newarray* - (list->vector (reverse (sort (vector->list *array*) <)) )) - -;; @@PLEAC@@_4.11 -(define *array* '#(1 2 3 4 5 6 7 8)) - -;; ------------ - -; Remove first 3 elements -(define *front* (vector-delete *array* 0 3)) - -; Remove last 3 elements -(define *end* (vector-delete *array* -1 3)) - -;; ---------------------------- - -; Another helper routine -(define (vector-slice vec pos len) - (let* ((vec-len (vector-length vec)) - (pos (if (< pos 0) (+ vec-len pos) pos)) - (in-bounds - (not (> (+ pos len) vec-len)))) - (if in-bounds - (let ((new-vec (make-vector len))) - (let loop ((vec-idx pos) (new-vec-idx 0)) - (cond - ((= new-vec-idx len) new-vec) - (else - (vector-set! new-vec new-vec-idx (vector-ref vec vec-idx)) - (loop (+ vec-idx 1) (+ new-vec-idx 1))) ))) - ;else - vec))) - -; Both the following use, 'values', to return two values; this approach -; is quite contrived and is taken to mimic the Perl examples, not -; because it is a recommended one [returning a single list would probably -; be more sensible] -(define (shift2 vec) - (let ((vec (vector-slice vec 0 2))) - (values (vector-ref vec 0) (vector-ref vec 1)) )) - -(define (pop2 vec) - (let ((vec (vector-slice vec -1 2))) - (values (vector-ref vec 0) (vector-ref vec 1)) )) - -;; ------------ - -(define *friends* '#('Peter 'Paul 'Mary 'Jim 'Tim)) - -(let-values ( ((this that) (shift2 *friends*)) ) - (print this ":" that)) - -;; ------------ - -(define *beverages* '#('Dew 'Jolt 'Cola 'Sprite 'Fresca)) - -(let-values ( ((d1 d2) (pop2 *beverages*)) ) - (print d1 ":" d2)) - -;; @@PLEAC@@_4.12 -; SRFI-1 [list manipulation] routines are ideal for the types of task -; in this and the next section, in particular, 'for-each' and 'find', -; 'list-index', and many others for more specialist functions. The same -; applies to vectors with the SRFI-43 routines, 'vector-index' and -; 'vector-skip', though the approach taken in this chapter has been to -; implement functionally similar vector manipulation routines to more -; closely mimic the Perl examples - -; Return #f, or first index for which 'pred' returns true -(define (vector-first-idx pred vec) - (let ((vec-len (vector-length vec))) - (let loop ((idx 0)) - (cond - ((= idx vec-len) #f) - (else - (if (pred (vector-ref vec idx)) - idx - ;else - (loop (+ idx 1))) ))))) - -; Return #f, or first index for which 'pred' returns true -(define (list-first-idx pred list) - (let loop ((idx 0) (list list)) - (cond - ((null? list) #f) - (else - (if (pred (car list)) - idx - ;else - (loop (+ idx 1) (cdr list))) )))) - -;; ------------ - -(define *array* '#(1 2 3 4 5 6 7 8)) - -(print - (vector-first-idx - (lambda (x) (= x 9)) - *array*)) - -;; ---- - -(define *list* '(1 2 3 4 5 6 7 8)) - -(print - (list-first-idx - (lambda (x) (= x 4)) - *list*)) - -;; ---- - -(use-modules (srfi srfi-1)) - -(print - (list-index - (lambda (x) (= x 4)) - *list*)) - -;; ---------------------------- - -; The Perl 'highest paid engineer' example isn't really a 'first match' -; type of problem - the routines shown earlier really aren't suited to -; this. Better suited, instead, are the SRFI-1 routines like 'fold', -; 'fold-right' and 'reduce', even old standbys like 'filter' and 'for-each' - -(define +null-salary-rec+ - (list '() 0 '())) - -(define *salaries* - (list - (list 'engineer 43000 'Bob) - (list 'programmer 48000 'Andy) - (list 'engineer 35000 'Champ) - (list 'engineer 49000 'Bubbles) - (list 'programmer 47000 'Twig) - (list 'engineer 34000 'Axel) )) - -;; ---------------------------- - -(define *highest-paid-engineer* - (reduce - (lambda (salary-rec acc) - (if - (and - (eq? (car salary-rec) 'engineer) - (> (cadr salary-rec) (cadr acc))) - salary-rec - ;else - acc)) - +null-salary-rec+ - *salaries*)) - -(print *highest-paid-engineer*) - -;; ------------ - -(define *highest-paid-engineer* - (fold-right - (lambda (salary-rec acc) - (if (> (cadr salary-rec) (cadr acc)) - salary-rec - ;else - acc)) - +null-salary-rec+ - (filter - (lambda (salary-rec) - (eq? (car salary-rec) 'engineer)) - *salaries*)) ) - -(print *highest-paid-engineer*) - -;; ------------ - -(define *highest-paid-engineer* +null-salary-rec+) - -(for-each - (lambda (salary-rec) - (if - (and - (eq? (car salary-rec) 'engineer) - (> (cadr salary-rec) (cadr *highest-paid-engineer*))) - (set! *highest-paid-engineer* salary-rec))) - *salaries*) - -(print *highest-paid-engineer*) - -;; @@PLEAC@@_4.13 -; All tasks in this section consist of either generating a collection, -; or filtering a larger collection, of elements matching some criteria; -; obvious candidates are the 'filter' and 'array-filter' routines, though -; others like 'for-each' can also be applied - -(define *list-matching* (filter PRED LIST)) -(define *vector-matching* (array-filter PRED ARRAY)) - -;; ---------------------------- - -(define *nums* '(1e7 3e7 2e7 4e7 1e7 3e7 2e7 4e7)) - -(define *bigs* - (filter - (lambda (num) (> num 1000000)) - *nums*)) - -;; ------------ - -(define *users* - (list - '(u1 . 2e7) - '(u2 . 1e7) - '(u3 . 4e7) - '(u4 . 3e7) )) - -(define *pigs* - (fold-right - (lambda (pair accum) (cons (car pair) accum)) - '() - (filter - (lambda (pair) (> (cdr pair) 1e7)) - *users*))) - -(print *pigs*) - -;; ------------ - -(define *salaries* - (list - (list 'engineer 43000 'Bob) - (list 'programmer 48000 'Andy) - (list 'engineer 35000 'Champ) - (list 'engineer 49000 'Bubbles) - (list 'programmer 47000 'Twig) - (list 'engineer 34000 'Axel) )) - -(define *engineers* - (filter - (lambda (salary-rec) - (eq? (car salary-rec) 'engineer)) - *salaries*)) - -(print *engineers*) - -;; ------------ - -(define *applicants* - (list - (list 'a1 26000 'Bob) - (list 'a2 28000 'Andy) - (list 'a3 24000 'Candy) )) - -(define *secondary-assistance* - (filter - (lambda (salary-rec) - (and - (> (cadr salary-rec) 26000) - (< (cadr salary-rec) 30000))) - *applicants*)) - -(print *secondary-assistance*) - -;; @@PLEAC@@_4.14 -; Sorting numeric data in Scheme is very straightforward ... - -(define *unsorted* '(5 8 1 7 4 2 3 6)) - -;; ------------ - -; Ascending sort - use '<' as comparator -(define *sorted* - (sort - *unsorted* - <)) - -(print *sorted*) - -;; ------------ - -; Descending sort - use '>' as comparator -(define *sorted* - (sort - *unsorted* - >)) - -(print *sorted*) - -;; @@PLEAC@@_4.15 -; A customised lambda may be passed as comparator to 'sort', so -; sorting on one or more 'fields' is quite straightforward - -(define *unordered* '( ... )) - -; COMPARE is some comparator suited for the element type being -; sorted -(define *ordered* - (sort - *unordered* - (lambda (left right) - (COMPARE left right)))) - -;; ------------ - -(define *unordered* - (list - (cons 's 34) - (cons 'e 12) - (cons 'c 45) - (cons 'q 11) - (cons 'g 24) )) - -(define *pre-computed* - (map - ; Here element is returned unaltered, but it would normally be - ; transformed in som way - (lambda (element) element) - *unordered*)) - -(define *ordered-pre-computed* - (sort - *pre-computed* - ; Sort on the first field [assume it is the 'key'] - (lambda (left right) - (string<? - (symbol->string (car left)) - (symbol->string (car right)))))) - -; Extract the second field [assume it is the 'value'] -(define *ordered* - (map - (lambda (element) (cdr element)) - *ordered-pre-computed*)) - -;; ---------------------------- - -(define *employees* - (list - (list 'Bob 43000 123 42) - (list 'Andy 48000 124 35) - (list 'Champ 35000 125 37) - (list 'Bubbles 49000 126 34) - (list 'Twig 47000 127 36) - (list 'Axel 34000 128 31) )) - -(define *ordered* - (sort - *employees* - (lambda (left right) - (string<? - (symbol->string (car left)) - (symbol->string (car right)))))) - -;; ------------ - -(for-each - (lambda (employee) - (print (car employee) "earns $" (cadr employee))) - (sort - *employees* - (lambda (left right) - (string<? - (symbol->string (car left)) - (symbol->string (car right)))))) - -;; ------------ - -(define *bonus* - (list - '(125 . 1000) - '(127 . 1500) )) - -(for-each - (lambda (employee) - (let ((bonus (assoc-ref *bonus* (caddr employee)))) - (if (not bonus) - '() - ;else - (print (car employee) "earned bonus" bonus) ))) - (sort - *employees* - (lambda (left right) - (string<? - (symbol->string (car left)) - (symbol->string (car right)))))) - -;; ---------------------------- - -(use-modules (srfi srfi-1) (ice-9 rdelim) (ice-9 regex)) - -(define *filename* "/etc/passwd") -(define *users* '()) - -(let ((port (open-input-file *filename*))) - (let loop ((line&terminator (read-line port 'split))) - (cond - ((eof-object? (cdr line&terminator)) '()) - (else - (set! *users* - (assoc-set! - *users* - (car (string-split (car line&terminator) #\:)) - #t)) - (loop (read-line port 'split)) ))) - (close-input-port port)) - -(for-each - (lambda (user) (print (car user))) - (sort - *users* - (lambda (left right) - (string<? - (car left) - (car right))))) - -;; @@PLEAC@@_4.16 -; Use SRFI-1's 'circular-list' routine to build a circular list -(use-modules (srfi srfi-1)) - -(define *processes* (circular-list 1 2 3 4 5)) - -(let loop ((processes *processes*)) - (print "Handling process" (car processes)) - (sleep 1) - (loop (cdr processes))) - -;; @@PLEAC@@_4.17 -(use-modules (srfi srfi-1)) - -; Implements Fischer-Yates shuffle algorithm -(define (vector-shuffle! vec) - (let ((vector-length (vector-length vec))) - (let loop ((i vector-length) (j (+ 1 (random vector-length)))) - (cond - ((= i 1) '()) - ((not (= i j)) - (vector-ref-swap! vec (- i 1) (- j 1)) - (loop (- i 1) (+ 1 (random (- i 1))))) - (else - (loop (- i 1) (+ 1 (random (- i 1))))) )))) - -(define (vector-ref-swap! vec idx1 idx2) - (let ((tmp (vector-ref vec idx1))) - (vector-set! vec idx1 (vector-ref vec idx2)) - (vector-set! vec idx2 tmp))) - -; Generate vector of values 1 .. 10 -(define *irange* (list->vector (iota 10 1 1))) - -; Shuffle array values -(vector-shuffle! *irange*) - -;; @@PLEAC@@_4.18 -;; @@INCOMPLETE@@ -;; @@INCOMPLETE@@ - -;; @@PLEAC@@_4.19 -;; @@INCOMPLETE@@ -;; @@INCOMPLETE@@ - -;; @@PLEAC@@_5.0 -;; --------------------------------------------------------------------- -;; Scheme offers two dictionary types: -;; -;; * Association list [list of pairs e.g. '((k1 . v1) (k2 . v2) ...)] -;; * Hash table [vector of pairs plus hash algorithm] -;; -;; Implementation differences aside, they are remarkably similar in that -;; the functions operating on them are similar named, and offer the same -;; interface. Examples: -;; -;; * Retrieve an item: (assoc-ref hash key) (hash-ref hash key) -;; * Update an item: (assoc-set! hash key value) (hash-set! hash key value) -;; -;; Hash tables would tend to be used where performance was critical e.g. -;; near constant-time lookups, or where entry updates are frequent, whilst -;; association lists would be used where table-level traversals and -;; manipulations require maximum flexibility -;; -;; Many of the sections include examples using both association lists and -;; hash tables. However, where only one of these is shown, implementing -;; the other is usually a trivial exercise. Finally, any helper functions -;; will be included in the Appendix -;; --------------------------------------------------------------------- - -; Association lists -(define *age* - (list - (cons 'Nat 24) - (cons 'Jules 25) - (cons 'Josh 17))) - -;; or, perhaps more compactly: -(define *age* - (list - '(Nat . 24) - '(Jules . 25) - '(Josh . 17))) - -;; ------------ - -; Guile built-in association list support -(define *age* (acons 'Nat 24 '())) -(set! *age* (acons 'Jules 25 *age*)) -(set! *age* (acons 'Josh 17 *age*)) - -;; ---- - -; SRFI-1 association list support -(use-modules (srfi srfi-1)) - -(define *age* (alist-cons 'Nat 24 '())) -(set! *age* (alist-cons 'Jules 25 *age*)) -(set! *age* (alist-cons 'Josh 17 *age*)) - -;; ------------ - -(define *food-colour* - (list - '(Apple . "red") - '(Banana . "yellow") - '(Lemon . "yellow") - '(Carrot . "orange"))) - -;; ---------------------------- - -; Hash tables. Guile offers an implementation, and it is also -; possible to use SRFI-69 hash tables; only the former will be -; illustrated here - -(define *age* (make-hash-table 20)) -; or -(define *age* (make-vector 20 '())) - -(hash-set! *age* 'Nat 24) -(hash-set! *age* 'Jules 25) -(hash-set! *age* 'Josh 17) - -(hash-for-each - (lambda (key value) (print key)) - *age*) - -; or, if vector used as hash table, can also use: - -(array-for-each - (lambda (pair) - (if (not (null? pair)) (print (car pair)))) - *age*) - -;; ------------ - -(define *food-colour* (make-hash-table 20)) - -(hash-set! *food-colour* 'Apple "red") -(hash-set! *food-colour* 'Banana "yellow") -(hash-set! *food-colour* 'Lemon "yellow") -(hash-set! *food-colour* 'Carrot "orange") - -;; @@PLEAC@@_5.1 -(set! *hash* (acons key value *hash*)) - -;; ------------ - -(set! *food-colour* (acons 'Raspberry "pink" *food-colour*)) - -(print "Known foods:") -(for-each - (lambda (pair) (print (car pair))) - *food-colour*) - -;; ---------------------------- - -(hash-set! *hash* key value) - -;; ------------ - -(hash-set! *food-colour* 'Raspberry "pink") - -(print "Known foods:") -(hash-for-each - (lambda (key value) (print key)) - *food-colour*) - -;; @@PLEAC@@_5.2 -; 'assoc' returns the pair, (key . value) -(if (assoc key hash) - ... found ... -;else - ... not found ... - -; 'assoc-ref' returns the value only -(if (assoc-ref hash key) - ... found ... -;else - ... not found ... - -;; ------------ - -; *food-colour* association list from an earlier section - -(for-each - (lambda (name) - (let ((pair (assoc name *food-colour*))) - (if pair - (print (symbol->string (car pair)) "is a food") - ;else - (print (symbol->string name) "is a drink") ))) - (list 'Banana 'Martini)) - -;; ---------------------------- - -; 'hash-get-handle' returns the pair, (key . value) -(if (hash-get-handle hash key) - ... found ... -;else - ... not found ... - -; 'hash-ref' returns the value only -(if (hash-ref hash key) - ... found ... -;else - ... not found ... - -;; ------------ - -; *food-colour* hash table from an earlier section - -(for-each - (lambda (name) - (let ((value (hash-ref *food-colour* name))) - (if value - (print (symbol->string name) "is a food") - ;else - (print (symbol->string name) "is a drink") ))) - (list 'Banana 'Martini)) - -;; ---------------------------- - -(define *age* (make-hash-table 20)) - -(hash-set! *age* 'Toddler 3) -(hash-set! *age* 'Unborn 0) -(hash-set! *age* 'Phantasm '()) - -(for-each - (lambda (thing) - (let ((value (hash-ref *age* thing))) - (display thing) - (if value (display " Exists")) - (if (and value (not (string-null? value))) (display " Defined")) - ; Testing for non-zero as true is not applicable, so testing - ; for non-equality with zero - (if (and value (not (eq? value 0))) (display " True")) - (print "") )) - (list 'Toddler 'Unborn 'Phantasm 'Relic)) - -;; @@PLEAC@@_5.3 -(assoc-remove! hash key) - -;; ------------ - -(use-modules (srfi srfi-1)) - -; *food-colour* association list from an earlier section - -(define (print-foods) - (let ((foods - (fold-right - (lambda (pair accum) (cons (car pair) accum)) - '() - *food-colour*))) - (display "Keys: ") (print foods) - (print "Values:") - (for-each - (lambda (food) - (let ((colour (assoc-ref *food-colour* food))) - (cond - ((string-null? colour) (display "(undef) ")) - (else (display (string-append colour " "))) ))) - foods)) - (newline)) - -(print "Initially:") -(print-foods) - -(print "\nWith Banana undef") -(assoc-set! *food-colour* 'Banana "") -(print-foods) - -(print "\nWith Banana deleted") -(assoc-remove! *food-colour* 'Banana) -(print-foods) - -;; ---------------------------- - -(hash-remove! hash key) - -;; ------------ - -(use-modules (srfi srfi-1)) - -; *food-colour* hash table from an earlier section - -(define (print-foods) - (let ((foods - (hash-fold - (lambda (key value accum) (cons key accum)) - '() - *food-colour*))) - (display "Keys: ") (print (reverse foods)) - (print "Values:") - (for-each - (lambda (food) - (let ((colour (hash-ref *food-colour* food))) - (cond - ((string-null? colour) (display "(undef) ")) - (else (display (string-append colour " "))) ))) - foods)) - (newline)) - -(print "Initially:") -(print-foods) - -(print "\nWith Banana undef") -(hash-set! *food-colour* 'Banana "") -(print-foods) - -(print "\nWith Banana deleted") -(hash-remove! *food-colour* 'Banana) -(print-foods) - -;; @@PLEAC@@_5.4 -; Since an association list is nothing more than a list of pairs, it -; may be traversed using 'for-each' -(for-each - (lambda (pair) - (let ((key (car pair)) - (value (cdr pair))) - ... do something with key / value ...)) - hash) - -;; ---------------------------- - -; A 'for-each'-like function is available for hash table traversal -(hash-for-each - (lambda (key value) - ... do something with key / value ...) - hash) - -; If the hash table is directly implemented as a vector, then it is -; also possible to traverse it using, 'array-for-each', though a -; check for empty slots is needed -(array-for-each - (lambda (pair) - (if (not (null? pair)) ... do something with key / value ...)) - hash) - -;; ---------------------------- - -; *food-colour* association list from an earlier section - -(for-each - (lambda (pair) - (let ((food (car pair)) - (colour (cdr pair))) - (print (symbol->string food) "is" colour) )) - *food-colour*) - -;; ------------ - -; *food-colour* association list from an earlier section - -(for-each - (lambda (food) - (print (symbol->string food) "is" (assoc-ref *food-colour* food))) - (sort - (fold-right - (lambda (pair accum) (cons (car pair) accum)) - '() - *food-colour*) - (lambda (left right) - (string<? (symbol->string left) (symbol->string right))))) - -;; ---------------------------- - -(use-modules (srfi srfi-1) (ice-9 rdelim) (ice-9 regex)) - -(define *filename* "from.txt") -(define *from* '()) - -(let ((port (open-input-file *filename*))) - (let loop ((line&terminator (read-line port 'split))) - (cond - ((eof-object? (cdr line&terminator)) '()) - (else - (let* ((key (string->symbol - (match:substring - (string-match - "^From: (.*)" (car line&terminator)) - 1) )) - (value (assoc-ref *from* key))) - (if (not value) (set! value 0)) - (set! *from* (assoc-set! *from* key (+ 1 value)))) - (loop (read-line port 'split)) ))) - (close-input-port port)) - -(for-each - (lambda (person) - (print (symbol->string person) ":" (number->string (assoc-ref *from* person)))) - (sort - (fold-right - (lambda (pair accum) (cons (car pair) accum)) - '() - *from*) - (lambda (left right) - (string<? (symbol->string left) (symbol->string right))))) - -;; @@PLEAC@@_5.5 -; All approaches shown in the previous section apply here also, so -; there is little to be gained by repeating those examples [i.e. the -; use of 'for-each' and similar]. It is always possible, of course, -; to directly recurse over an association list: - -; *food-colour* association list from an earlier section - -(define *sorted-food-colour* - (sort - *food-colour* - (lambda (left right) - (string<? - (symbol->string (car left)) - (symbol->string (car right)))) )) - -(let loop ((hash *sorted-food-colour*)) - (cond - ((null? hash) '()) - (else - (print - (symbol->string (car (car hash))) "=>" (cdr (car hash)) ) - (loop (cdr hash))) )) - -;; @@PLEAC@@_5.6 -; AFAIK, Scheme doesn't offer a facility similar to Perl's 'Tie::IxHash'. -; Therefore, use an association list if retrieval [from a dictionary -; type container] in insertion order is required. - -(define *food-colour* (acons 'Banana "Yellow" '())) -(set! *food-colour* (acons 'Apple "Green" *food-colour*)) -(set! *food-colour* (acons 'Lemon "yellow" *food-colour*)) - -(print "In insertion order, the foods are:") -(for-each - (lambda (pair) - (let ((food (car pair)) - (colour (cdr pair))) - (print " " (symbol->string food)) )) - *food-colour*) - -(print "Still in insertion order, the food's colours are:") -(for-each - (lambda (pair) - (let ((food (car pair)) - (colour (cdr pair))) - (print (symbol->string food) "is coloured" colour) )) - *food-colour*) - -;; ---------------------------- - -; Of course, insertion order is lost if the association list is sorted, -; or elements removed, so if maintaining insertion order is vital, it -; might pay to associate data with a timestamp [e.g. create a timestamped -; record / structure], and manipulate those entities [no example given] - -;; @@PLEAC@@_5.7 -(define *ttys* '()) - -(for-each - (lambda (user-tty-pair) - (let* ((user-tty-pair (string-split user-tty-pair #\space)) - (user (string->symbol (car user-tty-pair))) - (newtty (cadr user-tty-pair)) - (current-ttys (assoc-ref *ttys* user))) - (set! *ttys* - (assoc-set! *ttys* user - (if (not current-ttys) - newtty - (string-append current-ttys " " newtty)) )))) - (string-split (qx "who|cut -d' ' -f1,2") #\newline)) - -(for-each - (lambda (user-ttys) - (print (symbol->string (car user-ttys)) ":" (cdr user-ttys))) - (sort - *ttys* - (lambda (left right) - (string<? - (symbol->string (car left)) - (symbol->string (car right))))) ) - -;; ---------------------------- - -(use-modules (ice-9 regex)) - -(define (multi-hash-delete hash key value) - (let ((value-found (assoc-ref hash key))) - (if value-found - (assoc-ref hash key - (regexp-substitute/global - #f (string-match value value-found) 'pre "" 'post ""))))) - -;; @@PLEAC@@_5.8 -; Alternate implementatons of a hash inversion function; both assume -; key is a symbol, value is a string - -(define (assoc-invert assoc) - (map - (lambda (pair) - (cons - (string->symbol (cdr pair)) - (symbol->string (car pair)))) - assoc)) - -;; ------------ - -(define (assoc-invert assoc) - (let loop ((assoc assoc) (new-assoc '())) - (cond - ((null? assoc) new-assoc) - (else - (loop (cdr assoc) - (acons - (string->symbol (cdar assoc)) - (symbol->string (caar assoc)) new-assoc)) )) )) - -;; ---------------------------- - -(define *surname* - (list - '(Mickey . "Mantle") - '(Babe . "Ruth"))) - -(define *first-name* (assoc-invert *surname*)) - -(print (assoc-ref *first-name* 'Mantle)) - -;; ---------------------------- - -; foodfind - -(define *given* (string->symbol (cadr (command-line)))) - -(define *colour* - (list - '(Apple . "red") - '(Lemon . "yellow") - '(Carrot . "orange"))) - -(define *food* (assoc-invert *colour*)) - -(if (assoc-ref *colour* *given*) - (print - (symbol->string *given*) - "is a food with colour" - (assoc-ref *colour* *given*))) - -(if (assoc-ref *food* *given*) - (print - (assoc-ref *food* *given*) - "is a food with colour" - (symbol->string *given*))) - -;; @@PLEAC@@_5.9 -; *food-colour* association list from an earlier section - -; Use 'sort' to sort the entire hash, on key or on value, ascending or -; descending order -(define *sorted-on-key:food-colour* - (sort - *food-colour* - (lambda (left right) - (string<? - (symbol->string (car left)) - (symbol->string (car right)))) )) - -(define *sorted-on-value:food-colour* - (sort - *food-colour* - (lambda (left right) - (string<? - (cdr left) - (cdr right))) )) - -;; ------------ - -(for-each - (lambda (pair) - (let ((food (car pair)) - (colour (cdr pair))) - (print - (symbol->string food) - "is" - colour))) - *sorted-on-key:food-colour*) - -;; ---------------------------- - -; Alternatively, generate a list of keys or values, sort as required, -; and use list to guide the hash traversal - -(define *sorted-food-colour-keys* - (sort - (fold-right - (lambda (pair accum) (cons (car pair) accum)) - '() - *food-colour*) - (lambda (left right) - (string<? - (symbol->string left) - (symbol->string right))) )) - -(define *sorted-food-colour-values* - (sort - (fold-right - (lambda (pair accum) (cons (cdr pair) accum)) - '() - *food-colour*) - (lambda (left right) - (string<? left right)) )) - -;; ------------ - -(for-each - (lambda (food) - (print (symbol->string food) "is" (assoc-ref *food-colour* food))) - *sorted-food-colour-keys*) - -;; @@PLEAC@@_5.10 -; If merging is defined as the combining of the contents of two or more -; hashes, then it is simply a matter of copying the contents of each -; into a new hash - -; Association lists can simply be appended together -(define *food-colour* - (list - '(Apple . "red") - '(Banana . "yellow") - '(Lemon . "yellow") - '(Carrot . "orange"))) - -(define *drink-colour* - (list - '(Galliano . "yellow") - '(Mai Tai . "blue"))) - -(define *ingested-colour* (append *food-colour* *drink-colour*)) - -;; ---------------------------- - -; Hash tables built from vectors can be copied element by element into -; a new vector, or spliced together using 'vector-join' [see Chapter 4] - -(define *food-colour* (make-vector 20 '()) -; ... -(define *drink-colour* (make-vector 20 '()) -; ... - -(define *ingested-colour* - (vector-join *food-colour* *drink-colour*)) - -;; @@PLEAC@@_5.11 -(define *common* '()) -(define *this-not-that* '()) - -;; ------------ - -(define *dict1* - (list - '(Apple . "red") - '(Lemon . "yellow") - '(Carrot . "orange"))) - -(define *dict2* - (list - '(Apple . "red") - '(Carrot . "orange"))) - -;; ------------ - -; Find items common to '*dict1*' and '*dict2*' -(for-each - (lambda (pair) - (let ((key (car pair)) - (value (cdr pair))) - (if (assoc-ref *dict2* key) - (set! *common* (cons key *common*))) )) - *dict1*) - -;; ------------ - -; Find items in '*dict1*' but not '*dict2*' -(for-each - (lambda (pair) - (let ((key (car pair)) - (value (cdr pair))) - (if (not (assoc-ref *dict2* key)) - (set! *this-not-that* (cons key *this-not-that*))) )) - *dict1*) - -;; ---------------------------- - -(define *non-citrus* '()) - -(define *citrus-colour* - (list - '(Lemon . "yellow") - '(Orange . "orange") - '(Lime . "green"))) - -(define *food-colour* - (list - '(Apple . "red") - '(Banana . "yellow") - '(Lemon . "yellow") - '(Carrot . "orange"))) - -(for-each - (lambda (pair) - (let ((key (car pair)) - (value (cdr pair))) - (if (not (assoc-ref *citrus-colour* key)) - (set! *non-citrus* (cons key *non-citrus*))) )) - *food-colour*) - -;; @@PLEAC@@_5.12 -; All objects [including functions] are first class entities, so there -; is no problem / special treatment needed to use any object, including -; those classed as 'references' [e.g. file handles or ports] as keys - -(use-modules (srfi srfi-1) (srfi srfi-13)) - -(define *ports* '()) - -(for-each - (lambda (filename) - (let ((port (open-input-file filename))) - (set! *ports* (assoc-set! *ports* port filename)) )) - '("/etc/termcap" "/vmlinux" "/bin/cat")) - -(print - (string-append "open files: " - (string-drop - (fold-right - (lambda (pair accum) (string-append ", " (cdr pair) accum)) - "" - *ports*) - 2))) - -(for-each - (lambda (pair) - (let ((port (car pair)) - (filename (cdr pair))) - (seek port 0 SEEK_END) - (print filename "is" (number->string (ftell port)) "bytes long.") - (close-input-port port) )) - *ports*) - -;; @@PLEAC@@_5.13 -; An association list takes on the size of the number of elements with -; which it is initialised, so presizing is implicit - -(define *hash* '()) ; zero elements - -;; ------------ - -(define *hash* ; three elements - (list - '(Apple . "red") - '(Lemon . "yellow") - '(Carrot . "orange"))) - -;; ---------------------------- - -; A size [i.e. number of entries] must be specified when a hash table -; is created, so presizing is implicit - -(define *hash* (make-hash-table 100)) - -;; ------------ - -(define *hash* (make-vector 100 '())) - -;; @@PLEAC@@_5.14 -(define *array* - (list 'a 'b 'c 'd 'd 'a 'a 'c 'd 'd 'e)) - -;; ---------------------------- - -(define *count* '()) - -(for-each - (lambda (element) - (let ((value (assoc-ref *count* element))) - (if (not value) (set! value 0)) - (set! *count* (assoc-set! *count* element (+ 1 value))))) - *array*) - -;; ---------------------------- - -(define *count* (make-hash-table 20)) - -(for-each - (lambda (element) - (let ((value (hash-ref *count* element))) - (if (not value) (set! value 0)) - (hash-set! *count* element (+ 1 value)))) - *array*) - -;; @@PLEAC@@_5.15 -(define *father* - (list - '(Cain . Adam) - '(Abel . Adam) - '(Seth . Adam) - '(Enoch . Cain) - '(Irad . Enoch) - '(Mehujael . Irad) - '(Methusael . Mehujael) - '(Lamech . Methusael) - '(Jabal . Lamech) - '(Jubal . Lamech) - '(Tubalcain . Lamech) - '(Enos . Seth))) - -;; ------------ - -(use-modules (srfi srfi-1) (ice-9 rdelim)) - -(let ((port (open-input-file *filename*))) - (let loop ((line&terminator (read-line port 'split))) - (cond - ((eof-object? (cdr line&terminator)) '()) - (else - (let ((person (string->symbol (car line&terminator)))) - (let loop ((father (assoc-ref *father* person))) - (if father - (begin - (print father) - (loop (assoc-ref *father* father)) ))) - (loop (read-line port 'split)) )))) - (close-input-port port)) - -;; ------------ - -(use-modules (srfi srfi-1) (ice-9 rdelim)) - -(define (assoc-invert-N:M assoc) - (let ((new-assoc '())) - (for-each - (lambda (pair) - (let* ((old-key (car pair)) - (new-key (cdr pair)) - (new-key-found (assoc-ref new-assoc new-key))) - (if (not new-key-found) - (set! new-assoc (acons new-key (list old-key) new-assoc)) - ;else - (set! new-assoc (assoc-set! new-assoc new-key (cons old-key new-key-found))) ))) - assoc) - new-assoc)) - -(define *children* (assoc-invert-N:M *father*)) - -(let ((port (open-input-file *filename*))) - (let loop ((line&terminator (read-line port 'split))) - (cond - ((eof-object? (cdr line&terminator)) '()) - (else - (let* ((person (string->symbol (car line&terminator))) - (children-found (assoc-ref *children* person))) - (print (symbol->string person) "begat:") - (if (not children-found) - (print "nobody") - ;else - (for-each - (lambda (child) (print (symbol->string child) ",")) - children-found)) - (loop (read-line port 'split)) )))) - (close-input-port port)) - -;; @@PLEAC@@_5.16 -;; @@INCOMPLETE@@ -;; @@INCOMPLETE@@ - -;; @@PLEAC@@_7.0 -;; use (open-input-file filename) or (open filename O_RDONLY) - -(define input (open-input-file "/usr/local/widgets/data")) -(let loop ((line (read-line input 'concat))) - (cond ((not (eof-object? line)) - (if (string-match "blue" line) - (display line)) - (loop (read-line input 'concat))))) -(close input) - -;; Many I/O functions default to the logical STDIN/OUT - -;; You can also explicitly get the standard ports with -;; [set-]current-{input,output,error}-port. - -;; format takes a port as the first argument. If #t is given, format -;; writes to stdout, if #f is given, format returns a string. - -(let loop ((line (read-line))) ; reads from stdin - (cond ((not (eof-object? line)) - (if (not (string-match "[0-9]" line)) - ;; writes to stderr - (display "No digit found.\n" (current-error-port)) - ;; writes to stdout - (format #t "Read: ~A\n" line)) - (loop (read-line))))) - -;; use open-output-file - -(define logfile (open-output-file "/tmp/log")) - -;; increasingly specific ways of closing ports (it's safe to close a -;; closed port) - -(close logfile) ; #t -(close-port logfile) ; #f (already closed) -(close-output-port logfile) ; unspecified - -;; you can rebind standard ports with set-current-<foo>-port: - -(let ((old-out (current-output-port))) - (set-current-output-port logfile) - (display "Countdown initiated ...\n") - (set-current-output-port old-out) - (display "You have 30 seconds to reach minimum safety distance.\n")) - -;; or - -(with-output-to-file logfile - (lambda () (display "Countdown initiated ...\n"))) -(display "You have 30 seconds to reach minimum safety distance.\n") - - -;; @@PLEAC@@_7.1 -(define source (open-input-file path)) -(define sink (open-output-file path)) - -(define source (open path O_RDONLY)) -(define sink (open path O_WRONLY)) - -;;----------------------------- -(define port (open-input-file path)) -(define port (open-file path "r")) -(define port (open path O_RDONLY)) -;;----------------------------- -(define port (open-output-file path)) -(define port (open-file path "w")) -(define port (open path (logior O_WRONLY O_TRUNC O_CREAT))) -;;----------------------------- -(define port (open path (logior O_WRONLY O_EXCL O_CREAT))) -;;----------------------------- -(define port (open-file path "a")) -(define port (open path (logior O_WRONLY O_APPEND O_CREAT))) -;;----------------------------- -(define port (open path (logior O_WRONLY O_APPEND))) -;;----------------------------- -(define port (open path O_RDWR)) -;;----------------------------- -(define port (open-file path "r+")) -(define port (open path (logior O_RDWR O_CREAT))) -;;----------------------------- -(define port (open path (logior O_RDWR O_EXCL O_CREAT))) -;;----------------------------- - -;; @@PLEAC@@_7.2 -;; Nothing different needs to be done with Guile - -;; @@PLEAC@@_7.3 -(define expand-user - (let ((rx (make-regexp "^\\~([^/]+)?"))) - (lambda (filename) - (let ((m (regexp-exec rx filename))) - (if m - (string-append - (if (match:substring m 1) - (passwd:dir (getpwnam (match:substring m 1))) - (or (getenv "HOME") (getenv "LOGDIR") - (passwd:dir (getpwuid (cuserid))) "")) - (substring filename (match:end m))) - filename))))) - -;; @@PLEAC@@_7.4 -(define port (open-file filename mode)) ; raise an exception on error - -;; use catch to trap errors -(catch 'system-error ; the type of error thrown - (lambda () (set! port (open-file filename mode))) ; thunk to try - (lambda (key . args) ; exception handler - (let ((fmt (cadr args)) - (msg&path (caddr args))) - (format (current-error-port) fmt (car msg&path) (cadr msg&path)) - (newline)))) - -;; @@PLEAC@@_7.5 -;; use the POSIX tmpnam -(let ((name (tmpnam))) - (call-with-output-file name - (lambda (port) - ;; ... output to port - ))) - -;; better to test and be sure you have exclusive access to the file -;; (temp file name will be available as (port-filename port)) -(define (open-temp-file) - (let loop ((name (tmpnam))) - (catch 'system-error - (lambda () (open name (logior O_RDWR O_CREAT O_EXCL))) - (lambda (key . args) (loop (tmpnam)))))) - -;; or let mkstemp! do the work for you: -(define port (mkstemp! template-string-ending-in-XXXXXX)) - -(let* ((tmpl "/tmp/programXXXXXX") - (port (mkstemp! tmpl))) - ;; tmpl now contains the name of the temp file, - ;; e.g. "/tmp/programhVoEzw" - (do ((i 0 (1+ i))) - ((= i 10)) - (format port "~A\n" i)) - (seek port 0 SEEK_SET) - (display "Tmp file has:\n") - (do ((line (read-line port 'concat) (read-line port 'concat))) - ((eof-object? line)) - (display line)) - (close port)) - -;; @@PLEAC@@_7.6 -;; string ports are ideal for this - -(define DATA " -your data goes here -") - -(call-with-input-string - DATA - (lambda (port) - ;; ... process input from port - )) - -;; or - -(with-input-from-string DATA - (lambda () - ;; ... stdin now comes from DATA - )) - -;; @@PLEAC@@_7.7 -;; to process lines of current-input-port: -(do ((line (read-line) (read-line))) - ((eof-object? line)) - ;; ... do something with line - ) - -;; a general filter template: - -(define (body) - (do ((line (read-line) (read-line))) - ((eof-object? line)) - (display line) - (newline))) - -(let ((args (cdr (command-line)))) - ;; ... handle options here - (if (null? args) - (body) ; no args, just call body on stdin - (for-each ; otherwise, call body with stdin set to each arg in turn - (lambda (file) - (catch 'system-error - (lambda () - (with-input-from-file file - body)) - (lambda (key . args) - (format (current-error-port) (cadr args) (caaddr args) - (car (cdaddr args))) - (newline (current-error-port))))) - args))) - -;; example: count-chunks: -(use-modules (srfi srfi-1) (srfi srfi-13) (ice-9 format) (ice-9 regex)) - -;; also use directory-files from 9.5 and globbing functions from 9.6 - -;; can use (ice-9 getopt-long) described in chapter 15, or process -;; options by hand -(define opt-append 0) -(define opt-ignore-ints 0) -(define opt-nostdout 0) -(define opt-unbuffer 0) - -(define args (cdr (command-line))) - -(do ((opts args (cdr opts))) - ((or (null? opts) (not (eq? (string-ref (car opts) 0) #\-))) - (set! args opts)) - (let ((opt (car opts))) - (cond ((string=? opt "-a") (set! opt-append (1+ opt-append))) - ((string=? opt "-i") (set! opt-ignore-ints (1+ opt-ignore-ints))) - ((string=? opt "-n") (set! opt-nostdout (1+ opt-nostdout))) - ((string=? opt "-u") (set! opt-unbuffer (1+ opt-unbuffer))) - (else (throw 'usage-error "Unexpected argument: ~A" opt))))) - -;; default to all C source files -(if (null? args) (set! args (glob "*.[Cch]" "."))) - -(define (find-login) - (do ((line (read-line) (read-line))) - ((eof-object? line)) - (cond ((string-match "login" line) - (display line) - (newline))))) - -(define (lowercase) - (do ((line (read-line) (read-line))) - ((eof-object? line)) - (display (string-downcase line)) - (newline))) - -(define (count-chunks) - (do ((line (read-line) (read-line)) - (chunks 0)) - ((or (eof-object? line) - (string=? line "__DATA__") (string=? line "__END__")) - (format #t "Found ~A chunks\n" chunks)) - (let ((tokens - (string-tokenize (string-take line (or (string-index line #\#) - (string-length line)))))) - (set! chunks (+ chunks (length tokens)))))) - -(if (null? args) - (count-chunks) ; or find-login, lowercase, etc. - (for-each - (lambda (file) - (catch 'system-error - (lambda () - (with-input-from-file file - count-chunks)) - (lambda (key . args) - (format (current-error-port) (cadr args) (caaddr args) - (car (cdaddr args))) - (newline (current-error-port))))) - args)) - -;; @@PLEAC@@_7.8 -;; write changes to a temporary file then rename it -(with-input-from-file old - (lambda () - (with-output-to-file new - (lambda () - (do ((line (read-line) (read-line))) - ((eof-object? line)) - ;; change line, then... - (write-line line)))))) -(rename-file old (string-append old ".orig")) -(rename-file new old) - -;; @@PLEAC@@_7.9 -;; no -i switch - -;; @@PLEAC@@_7.10 -;; open the file in read/write mode, slurp up the contents, modify it, -;; then write it back out: -(let ((p (open-file file "r+")) - (lines '())) - ;; read in lines - (do ((line (read-line p) (read-line p))) - ((eof-object? line)) - (set! lines (cons line lines))) - ;; modify (reverse lines) - (seek p 0 SEEK_SET) - ;; write out lines - (for-each (lambda (x) (write-line x p)) lines) - ;; truncate the file - (truncate-file p) - (close p)) - -(let ((p (open-file "foo" "r+")) - (lines '()) - (date (date->string (current-date)))) - (do ((line (read-line p 'concat) (read-line p 'concat))) - ((eof-object? line)) - (set! lines (cons line lines))) - (seek p 0 SEEK_SET) - (for-each - (lambda (x) - (regexp-substitute/global p "DATE" x 'pre date 'post)) - (reverse lines)) - (truncate-file p) - (close p)) - -;; @@PLEAC@@_7.11 -(define p (open-file path "r+")) -(flock p LOCK_EX) -;; update the file, then... -(close p) - -;; to increment a number in a file -(define p (open "numfile" (logior O_RDWR O_CREAT))) -(flock p LOCK_EX) -;; Now we have acquired the lock, it's safe for I/O -(let* ((obj (read p)) - (num (if (eof-object? obj) 0 obj))) - (seek p 0 SEEK_SET) - (truncate-file p) - (write (1+ num) p) - (newline p)) -(close p) - -;; @@PLEAC@@_7.12 -;; use force-output -(force-output p) - -;; flush all open ports -(flush-all-ports) - -;; @@PLEAC@@_7.13 -;; use select -(select inputs outputs exceptions seconds) -(select (list p1 p2 p3) '() '()) - -(let* ((nfound (select (list inport) '() '())) - (inputs (car nfound))) - (if (not (null? inputs)) - (let ((line (read-line inport))) - (format #t "I read ~A\n" line)))) - -;; or use char-ready? if you only need a single character -(if (char-ready? p) - (format #t "I read ~A\n" (read-char p))) - -;; @@PLEAC@@_7.14 -;; use the O_NONBLOCK option with open -(define modem (open "/dev/cua0" (logior O_RDWR O_NONBLOCK))) - -;; or use fcntl if you already have a port -(let ((flags (fcntl p F_GETFD))) - (fcntl p F_SETFD (logior flags O_NONBLOCK))) - -;; @@PLEAC@@_7.15 -;; use stat -(let ((buf (make-string (stat:size (stat p))))) - (read-string!/partial buf input)) - -;; @@PLEAC@@_7.16 -;; not needed - ports are first class objects - -;; @@PLEAC@@_7.18 -;; use for-each on the list of ports: -(for-each (lambda (p) (display stuff-to-print p)) port-list) - -;; or, if you don't want to keep track of the port list and know you -;; want to print to all open output ports, you can use port-for-each: -(port-for-each (lambda (p) (if (output-port? p) (display stuff p)))) - -;; @@PLEAC@@_7.19 -;; use fdopen: -(define p (fdopen num mode)) -(define p (fdopen 3 "r")) - -(define p (fdopen (string->number (getenv "MHCONTEXTFD")) "r")) -;; after processing -(close p) - -;; @@PLEAC@@_7.20 -;; ports are first class objects and can be aliased and passed around -;; like any other non-immediate variables: -(define alias original) -(define old-in (current-input-port)) - -;; or you can open two separate ports on the same file: -(define p1 (open-input-file path)) -(define p2 (open-input-file path)) - -;; or use fdopen: -(define copy-of-p (fdopen (fileno p) mode)) - -(define old-out (current-output-port)) -(define old-err (current-error-port)) - -(define new-out (open-output-file "/tmp/program.out")) - -(set-current-output-port new-out) -(set-current-error-port new-out) - -(system joe-random-program) - -(close new-out) - -(set-current-output-port old-out) -(set-current-error-port old-out) - -;; @@PLEAC@@_8.0 -;; open the file and loop through the port with read-line: -(let ((p (open-input-file file))) - (do ((line (read-line p) (read-line p))) - ((eof-object? line)) - (format #t "~A\n" (string-length line))) - (close p)) - -;; you can use with-input-from-file to temporarily rebind stdin: -(with-input-from-file file - (lambda () - (do ((line (read-line) (read-line))) - ((eof-object? line)) - (format #t "~A\n" (string-length line))))) - -;; or define a utility procedure to do this -(define (for-each-line proc file) - (with-input-from-file file - (lambda () - (do ((line (read-line) (read-line))) - ((eof-object? line)) - (proc line))))) -(for-each-line (lambda (x) (format #t "~A\n" (string-length line))) file) - -;; read in the file as a list of lines -(define (read-lines file) - (let ((ls '())) - (with-input-from-file file - (lambda () - (do ((line (read-line) (read-line))) - ((eof-object? line)) - (set! ls (cons line ls))) - (reverse ls))))) - -;; read in the file as a single string -(define (file-contents file) - (call-with-input-file file - (lambda (p) - (let* ((size (stat:size (stat p))) - (buf (make-string size))) - (read-string!/partial buf p) - buf)))) - -;; use display to print human readable output -(display '("One" "two" "three") port) ; (One two three) -(display "Baa baa black sheep.\n") ; Sent to default output port - -;; use write to print machine readable output -(write '("One" "two" "three") port) ; ("One" "two" "three") - -;; use (ice-9 rw) to read/write fixed-length blocks of data: -(use-modules (ice-9 rw)) -(let ((buffer (make-string 4096))) - (read-string!/partial buffer port 4096)) - -;; truncate-file -(truncate-file port length) ; truncate to length -(truncate-file port) ; truncate to current pos - -;; ftell -(define pos (ftell port)) -(format #t "I'm ~A bytes from the start of DATAFILE.\n" pos) - -;; seek -(seek log-port 0 SEEK_END) ; seek to end -(seek data-port pos SEEK_SET) ; seek to pos -(seek out-port -20 SEEK_CUR) ; seek back 20 bytes - -;; block read/write -(use-modules (ice-9 rw)) -(write-string/partial mystring data-port (string-length mystring)) -(read-string!/partial block 256 5) - -;; @@PLEAC@@_8.1 -(let ((rx (make-regexp "(.*)\\\\$"))) ; or "(.*)\\\\\\s*$" - (with-input-from-file file - (lambda () - (let loop ((line (read-line))) - (if (not (eof-object? line)) - (let ((m (regexp-exec rx line)) - (next (read-line))) - (cond ((and m (not (eof-object? next))) - (loop (string-append (match:substring m 1) next))) - (else - ;; else process line here, then recurse - (loop next))))))))) - -;; @@PLEAC@@_8.2 -(do ((line (read-line p) (read-line p)) - (i 0 (1+ i))) - ((eof-object? line) i)) - -;; fastest way if your terminator is a single newline -(use-modules (ice-9 rw) (srfi srfi-13)) -(let ((buf (make-string (expt 2 16))) - (count 0)) - (do ((len (read-string!/partial buf p) (read-string!/partial buf p))) - ((not len) count) - (set! count (+ count (string-count buf #\newline 0 len))))) - -;; or use port-line -(let loop ((line (read-line p))) - (if (eof-object? line) (port-line p) (loop (read-line p)))) - -;; @@PLEAC@@_8.3 -;; default behaviour of string-tokenize is to split on whitespace: -(use-modules (srfi srfi-13)) -(let loop ((line (read-line p))) - (cond ((not eof-object? line) - (for-each some-function-of-word (string-tokenize line)) - (loop (read-line p))))) - -(let ((table (make-hash-table 31))) - (let loop ((line (read-line p))) - (cond ((not (eof-object? line)) - (for-each - (lambda (w) (hash-set! table w (1+ (hash-ref table w 0)))) - (string-tokenize line)) - (loop (read-line p))))) - (hash-fold (lambda (k v p) (format #t "~5D ~A\n" v k)) #f table)) - -;; @@PLEAC@@_8.4 -;; build up the list the reverse it or fold over it: -(define lines (read-lines file)) -(for-each (lambda (word) do-something-with-word) (reverse lines)) -(fold (lambda (word acc) do-something-with-word) #f lines) - -;; @@PLEAC@@_8.5 -;; save the current position and reseek to it -(define (tail file) - (call-with-input-file file - (lambda (p) - (let loop ((line (read-line p))) - (cond ((eof-object? line) - (sleep sometime) - (let ((pos (ftell p))) - (seek p 0 SEEK_SET) - (seek p pos SEEK_SET))) - (else - ;; process line - )) - (loop (read-line p)))))) - -;; @@PLEAC@@_8.6 -(let ((rand-line #f)) - (let loop ((line (read-line p))) - (cond ((not (eof-object? line)) - (if (= 0 (random (port-line p))) - (set! rand-line line)) - (loop (read-line p))))) - ;; rand-line is the random line - ) - -;; @@PLEAC@@_8.7 -(define (shuffle list) - (let ((v (list->vector list))) - (do ((i (1- (vector-length v)) (1- i))) - ((< i 0) (vector->list v)) - (let ((j (random (1+ i)))) - (cond ((not (= i j)) - (let ((temp (vector-ref v i))) - (vector-set! v i (vector-ref v j)) - (vector-set! v j temp)))))))) - -(define rand-lines (shuffle (read-lines file)) - -;; @@PLEAC@@_8.8 -;; looking for line number desired-line-number -(do ((line (read-line p) (read-line p))) - ((= ((port-line p) desired-line-number) line))) -;; or read into a list -(define lines (read-lines file)) -(list-ref lines desired-line-number) - -;; @@INCOMPLETE@@ -; (define (build-index data-file index-file) -; ) - -; (define (line-with-index data-file index-file line-number) -; ) - -;; @@PLEAC@@_8.9 -;; use string-tokenize with an appropriate character set -(use-modules (srfi srfi-13) (srfi srfi-14)) -(define fields (string-tokenize line (string->charset "+-"))) -(define fields (string-tokenize line (string->charset ":"))) -(define fields (string-tokenize line)) - -;; @@PLEAC@@_8.10 -(let ((p (open-file file "r+"))) - (let ((pos 0)) - (let loop ((line (read-line p))) - (cond ((eof-object? (peek-char p)) - (seek p 0 SEEK_SET) - (truncate-file p pos) - (close p)) - (else - (set! pos (ftell p)) - (loop (read-line p))))))) - -;; @@PLEAC@@_8.11 -;; no equivalent - don't know how Guile under windows handles this - -;; @@PLEAC@@_8.12 -(let* ((address (* recsize recno)) - (buf (make-string recsize))) - (seek p address SEEK_SET) - (read-string!/partial buf p) - buf) - -;; @@PLEAC@@_8.13 -(let* ((address (* recsize recno)) - (buf (make-string recsize))) - (seek p address SEEK_SET) - (read-string!/partial buf p) - ;; modify buf, then write back with - (seek p address SEEK_SET) - (write-string/partial buf p) - (close p)) - -;; @@INCOMPLETE@@ -;; weekearly - -;; @@PLEAC@@_8.14 -(seek p addr SEEK_SET) -(define str (read-delimited (make-string 1 #\nul) p)) - -#!/usr/local/bin/guile -s -!# -;; bgets -- get a string from an address in a binary file -(use-modules (ice-9 format)) - -(define args (cdr (command-line))) -(define file (car args)) -(define addrs (map string->number (cdr args))) -(define delims (make-string 1 #\nul)) - -(call-with-input-file file - (lambda (p) - (for-each - (lambda (addr) - (seek p addr SEEK_SET) - (format #t "~X ~O ~D ~S\n" addr addr addr - (read-delimited delims p))) - addrs))) - -;; @@INCOMPLETE@@ -;; strings - -;; @@PLEAC@@_9.0 -(define entry (stat "/usr/bin/vi")) -(define entry (stat "/usr/bin")) -(define entry (stat port)) - -(use-modules (ice-9 posix)) - -(define inode (stat "/usr/bin/vi")) -(define ctime (stat:ctime inode)) -(define size (stat:size inode)) - -(define F (open-input-file filename)) -;; no equivalent - what defines -T? -; unless (-s F && -T _) { -; die "$filename doesn't have text in it.\n"; -; } - -(define dir (opendir "/usr/bin")) -(do ((filename (readdir dir) (readdir dir))) - ((eof-object? filename)) - (format #t "Inside /usr/bin is something called ~A\n" filename)) -(closedir dir) - -;; @@PLEAC@@_9.1 -(define inode (stat filename)) -(define readtime (stat:atime inode)) -(define writetime (stat:mtime inode)) - -(utime newreadtime newwritetime filename) - -(define seconds-per-day (* 60 60 24)) -(define inode (stat file)) -(define atime (stat:atime inode)) -(define mtime (stat:mtime inode)) -(set! atime (- atime (* 7 seconds-per-day))) -(set! mtime (- mtime (* 7 seconds-per-day))) -(utime file atime mtime) - -;; mtime is optional -(utime file (current-time)) -(utime file (stat:atime (stat file)) (current-time)) - -#!/usr/local/bin/guile -s -!# -;; uvi - vi a file without changing its access times - -(define file (cadr (command-line))) -(define inode (stat file)) -(define atime (stat:atime inode)) -(define mtime (stat:mtime inode)) -(system (string-append (or (getenv "EDITOR") "vi") " " file)) -(utime file atime mtime) - -;; @@PLEAC@@_9.2 -(delete-file file) - -(let ((count 0)) - (for-each - (lambda (x) - (catch #t - (lambda () (delete-file x) (set! count (1+ count))) - (lambda (err . args) #f))) - file-list) - (if (not (= count (length file-list))) - (format (current-error-port) "could only delete ~A of ~A files" - count (length file-list)))) - -;; @@PLEAC@@_9.3 -;; use builtin copy-file -(copy-file oldfile newfile) -(rename-file oldfile newfile) - -;; or do it by hand (clumsy, error-prone) -(use-modules (ice-9 rw) (ice-9 posix)) -(with-input-from-file oldfile - (lambda () - (call-with-output-file newfile - (lambda (p) - (let* ((inode (stat oldfile)) - (blksize (if inode (stat:size inode) 16384)) - (buf (make-string blksize))) - (let loop ((len (read-string!/partial buf))) - (cond ((and len (> len 0)) - (write-string/partial buf p 0 len) - (loop (read-string!/partial buf)))))))))) - -;; or call out to the system (non-portable, insecure) -(system (string-append "cp " oldfile " " newfile)) ; unix -(system (string-append "copy " oldfile " " newfile)) ; dos, vms - -;; @@PLEAC@@_9.4 -;; use a hash lookup of inodes -(use-modules (ice-9 posix)) -(let ((seen (make-hash-table 31))) - (for-each - (lambda (file) - (let* ((stats (stat file)) - (key (cons (stat:dev stats) (stat:ino stats))) - (val (hash-ref seen key 0))) - (cond ((= val 0) - ;; do something with new file - )) - (hash-set! seen key (1+ val)))) - file-names)) - -(let ((seen (make-hash-table 31))) - (for-each - (lambda (file) - (let* ((stats (stat file)) - (key (cons (stat:dev stats) (stat:ino stats))) - (val (hash-ref seen key '()))) - (hash-set! seen key (cons file val)))) - file-names) - (hash-fold - (lambda (key value prior) - ;; process key == (dev . inode), value == list of filenames - ) - '() seen)) - -;; @@PLEAC@@_9.5 -;; use opendir, readdir, closedir -(let ((p (opendir dir))) - (let loop ((file (readdir p))) - (if (eof-object? file) - (close p) - ;; do something with file - ))) - -;; or define a utility function for this -(define (directory-files dir) - (if (not (access? dir R_OK)) - '() - (let ((p (opendir dir))) - (do ((file (readdir p) (readdir p)) - (ls '())) - ((eof-object? file) (closedir p) (reverse! ls)) - (set! ls (cons file ls)))))) - -;; to skip . and .. -(cddr (directory-files dir)) - -;; probably better to implement full Emacs style directory-files -(use-modules (ice-9 posix)) -(define plain-files - (let ((rx (make-regexp "^\\."))) - (lambda (dir) - (sort (filter (lambda (x) (eq? 'regular (stat:type (stat x)))) - (map (lambda (x) (string-append dir "/" x)) - (remove (lambda (x) (regexp-exec rx x)) - (cddr (directory-files dir))))) - string<)))) - -;; @@PLEAC@@_9.6 -(define (glob->regexp pat) - (let ((len (string-length pat)) - (ls '("^")) - (in-brace? #f)) - (do ((i 0 (1+ i))) - ((= i len)) - (let ((char (string-ref pat i))) - (case char - ((#\*) (set! ls (cons "[^.]*" ls))) - ((#\?) (set! ls (cons "[^.]" ls))) - ((#\[) (set! ls (cons "[" ls))) - ((#\]) (set! ls (cons "]" ls))) - ((#\\) - (set! i (1+ i)) - (set! ls (cons (make-string 1 (string-ref pat i)) ls)) - (set! ls (cons "\\" ls))) - (else - (set! ls (cons (regexp-quote (make-string 1 char)) ls)))))) - (string-concatenate (reverse (cons "$" ls))))) - -(define (glob pat dir) - (let ((rx (make-regexp (glob->regexp pat)))) - (filter (lambda (x) (regexp-exec rx x)) (directory-files dir)))) - -(define files (glob "*.c" ".")) -(define files (glob "*.[ch]" ".")) - -;; Not sure if the Schwartzian Transform would really be more -;; efficient here... perhaps with a much larger directory where very -;; few files matched. -(define dirs (filter - (lambda (x) (eq? 'directory (stat:type (stat x)))) - (map (lambda (x) (string-append dir "/" x)) - (sort (filter (lambda (x) (string-match "^[0-9]+$" x)) - (directory-files dir)) - (lambda (a b) - (< (string->number a) (string->number b))))))) - -;; @@PLEAC@@_9.7 -(define (find proc . dirs) - (cond ((pair? dirs) - (for-each proc (map (lambda (x) (string-append (car dirs) "/" x)) - (directory-files (car dirs)))) - (apply find proc (cdr dirs))))) - -(find (lambda (x) (format #t "~A~A\n" x - (if (equal? (stat:type (stat x)) 'directory) - "/" ""))) ".") - -(define saved-size -1) -(define saved-name "") -(define (biggest file) - (let ((stats (stat file))) - (if (eq? (stat:type stats) 'regular) - (let ((size (stat:size (stat file)))) - (cond ((> size saved-size) - (set! saved-size size) - (set! saved-name file))))))) -(apply find biggest (cdr (command-line))) -(format #t "Biggest file ~A in ~A is ~A bytes long.\n" - saved-name (cdr (command-line)) saved-size) - -#!/usr/local/bin/guile -s -!# -;; fdirs - find all directories -(define (print-dirs f) - (if (eq? (stat:type (stat f)) 'directory) - (write-line f))) -(apply find print-dirs (cdr (command-line))) - -;; @@PLEAC@@_9.8 -#!/usr/local/bin/guile -s -!# -;; rmtree - remove whole directory trees like rm -f -(define (finddepth proc . dirs) - (cond ((pair? dirs) - (apply finddepth proc (cdr dirs)) - (for-each proc (map (lambda (x) (string-append (car dirs) "/" x)) - (directory-files (car dirs))))))) -(define (zap f) - (let ((rm (if (eq? (stat:type (stat f)) 'directory) rmdir delete-file))) - (format #t "deleting ~A\n" f) - (catch #t - (lambda () (rm f)) - (lambda args (format #t "couldn't delete ~A\n" f))))) -(let ((args (cdr (command-line)))) - (if (null? args) - (error "usage: rmtree dir ..\n") - (apply finddepth zap args))) - -;; @@PLEAC@@_9.9 -(for-each - (lambda (file) - (let ((newname (function-of file))) - (catch #t - (lambda () (rename-file file newname)) - (lambda args (format (current-error-port) - "couldn't rename ~A to ~A\n" file newname))))) - names) - -#!/usr/local/bin/guile -s -!# -;; rename - Guile's filename fixer -(use-modules (ice-9 regex)) ; not needed, but often useful here -(define args (cdr (command-line))) -(if (null? args) (error "usage: rename expr [files]\n")) -(define proc (eval-string (car args))) -(for-each - (lambda (old) - (let ((new (proc old))) - (if (not (string=? old new)) - (catch #t - (lambda () (rename-file old new)) - (lambda args (format (current-error-port) - "couldn't rename ~A to ~A\n" old new)))))) - (cdr args)) - -;; command-line examples: -;; rename '(lambda (x) (regexp-substitute/global #f "\\.orig\$" x (quote pre)))' *.orig -;; rename string-downcase * -;; rename '(lambda (x) (if (string-match "^Make" x) x (string-downcase x)))' * -;; rename '(lambda (x) (string-append x ".bad"))' *.pl -;; rename '(lambda (x) (format #t "~a: ") (read-line))' * - -;; @@PLEAC@@_9.10 -(define base (basename path)) -(define base (dirname path ext)) -(define dir (dirname path)) - -(define path "/usr/lib/libc.a") -(define file (basename path)) -(define dir (dirname path)) - -(format #t "dir is ~A, file is ~A\n" dir file) - -(basename path ".a") ; libc - -(use-modules (ice-9 regex)) -(define (file-parse path . args) - (let* ((ext (if (null? args) "\\..*" (car args))) - (rx1 (string-append "^((.*)/)?(.*)?(" ext ")$")) - (rx2 (string-append "^((.*)/)?(.*)?()$"))) - (let ((m (or (string-match rx1 path) (string-match rx2 path)))) - (list (match:substring m 2) (match:substring m 3) - (match:substring m 4))))) - -(define (extension path . args) - (caddr (apply file-parse path args))) - -;; @@PLEAC@@_10.0 -; Note: Some of the examples will show code blocks in this style: -; -; (define -; ... code here ... -; ) -; -; This is not generally considered good style, and is not recommended; -; it is only used here to more clearly highlight block scope - -; By convention a 'global variable' i.e. a variable that is defined at -; the top-level, and as such, visible within any scope, is named with -; beginning and ending asterisks [and one to be used as a constant -; with beginning and ending plus signs] - -(define *greeted* 0) - -(define (hello) - (set! *greeted* (+ *greeted* 1)) - (print "hi there!, this procedure has been called" *greeted* "times")) - -(define (how-many-greetings) *greeted*) - -;; ------------ - -(hello) - -(define *greetings* (how-many-greetings)) - -(print "bye there!, there have been" *greetings* "greetings so far") - -;; @@PLEAC@@_10.1 -; Subroutine parameters are named [whether directly, or indirectly in -; the case of variable arguments - see next example]; this is the only -; means of access [This contrasts with languages like Perl and REXX which -; allow access to arguments via array subscripting, and function calls, -; respectively] -(define (hypotenuse side1 side2) - (sqrt (sum (* side1 side1) (* side2 side2)))) - -(define *diag* (hypotenuse 3 4)) - -;; ---- - -; 'other-sides' is the name of a list of containing any additional -; parameters. Note that a name is still used to access values -(define (hypotenuse side1 . other-sides) - (let ((all-sides (cons side1 other-sides))) - (for-each - (lambda (side) ...) - all-sides) - ...)) - -;; ---- - -(define *diag* (hypotenuse 3 4)) - -;; ---- - -; Possible to pack parameters into a single structure [e.g. list or -; array], and access values contained therein -(define (hypotenuse sides) - (let ((side1 (car sides)) (side2 (caar sides))) - (sqrt (sum (* side1 side1) (* side2 side2))))) - -;; ---- - -(define *args* '(3 4)) -(define *diag* (hypotenuse *args*)) - -;; ------------ - -; Parameters passed by reference, however, whether original object is -; modified depends on choice of functions used to manipulate them -; [most functions create copies and return these; mutating versions of -; same functions may also exist [see next example] -(define *nums* (vector 1.4 3.5 6.7)) - -(define (int-all vec) - (vector-map-in-order - (lambda (element) (inexact->exact (round element))) - vec)) - -; Copy created -(define *ints* (int-all *nums*)) - -(print *nums*) -(print *ints*) - -;; ---- - -(define *nums* (vector 1.4 3.5 6.7)) - -(define (trunc-all vec) - (array-map-in-order! - (lambda (element) (inexact->exact (round element))) - vec)) - -; Original modified -(trunc-all *nums*) - -;; @@PLEAC@@_10.2 -; Scheme is lexically-scoped; variables defined within a block are -; visible only within that block. Whilst nested / subordinate blocks -; have access to those variables, neither the caller, nor any called -; procedures have direct access to those same variables - -(define (some-func parm1 parm2 parm3) - ... paramaters visible here ... - - (let ((var1 ...) (var2 ...) (var3 ...) ...) - ... parameters also visible here, but variables, 'var1' etc - only visible within this block ... - ) - ... paramaters also visible here, but still within procedure body ... -) - -;; ------------ - -; Top-level definitions - accessable globally -(define *name* (caar (command-line))) -(define *age* (cadr (command-line))) - -(define *start* (fetch-time)) - -;; ---- - -; Lexical binding - accessable only within this block -(let ((name (caar (command-line))) - (age (cadr (command-line))) - (start (fetch-time))) - ... variables only visible here ... -) - -;; ------------ - -(define *pair* '(1 . 2)) - -; 'a' and 'b' need to be dereferenced and separately defined [Also, -; since globally defined, should really be named, '*a*', '*b*', etc] -(define a (car *pair*)) -(define b (cdr *pair*)) -(define c (fetch-time)) - -(define (run-check) - ... do something with 'a', 'b', and 'c' ... -) - -(define (check-x x y) - (if (run-check) - (print "got" x))) - -; Calling 'check-x'; 'run-check' has access to 'a', 'b', and 'c' -(check-x ...) - -;; ---- - -; If defined within a block, variables 'a', 'b', and 'c' are no longer -; accessable anywhere except that scope. Therefore, 'run-check' as -; defined above can no longer access these variables [in fact, the code -; will fail because variables 'a', 'b', and 'c' do not exist when -; 'run-check' is defined] -(let ((a (car *pair*)) - (b (cdr *pair*)) - (c (fetch-time))) - ... - (check-x ...) - ... -) - -;; ---- - -; The procedures, 'run-check' and 'check-x' are defined within the -; same block as variables, 'a', 'b', and 'c', so have direct access to -; them -(let* ((a (car *pair*)) - (b (cdr *pair*)) - (c (fetch-time)) - - (run-check - (lambda () ... do something with 'a', 'b', and 'c' ...)) - - (check-x - (lambda (x y) - (if (run-check) - (print "got" x)))) ) - ... - (check-x ...) - ... -) - -;; @@PLEAC@@_10.3 -; Ordinarily, a variable must be initialised when it is defined, -; whether at the top-level: -(define *variable* 1) - -; ... or within a 'let' binding -(let* ((variable 1) - (mysub - (lambda () ... accessing 'variable' ...))) - ... do stuff ... -) - -; However, since Scheme allows syntactic extensions via 'macros' [of -; which there are two varieties: hygenic and LISP-based], it is -; possible to create new forms which alter this behaviour. For example, -; in this tutorial: http://home.comcast.net/~prunesquallor/macro.txt -; there is a macro implementation equivalent to 'let, 'called, -; 'bind-values', which allows variables to be defined without initial -; values; an example follows: - -; Initialisation values for 'a' and 'b' not specified -(bind-values ((a) b (c (+ *global* 5))) - ... do stuff ... -) - -; In Scheme many things are possible, but not all those things are -; offered as standard features :) ! - -;; ------------ - -(let* ((counter 42) - (next-counter - (lambda () (set! counter (+ counter 1)) counter)) - (prev-counter - (lambda () (set! counter (- counter 1)) counter))) - - ... do stuff with 'next-counter' and 'prev-counter' ... -) - -;; ---- - -; A more complete, and practical, variation of the above code: - -; 'counter' constructor -(define (make-counter start) - (let* ((counter 42) - (next-counter - (lambda () (set! counter (+ counter 1)) counter)) - (prev-counter - (lambda () (set! counter (- counter 1)) counter))) - (lambda (op) - (cond - ((eq? op 'prev) prev-counter) - ((eq? op 'next) next-counter) - (else (lambda () (display "error:counter"))) )))) - -; Interface functions to 'counter' functionality -(define (prev-counter counter) (apply (counter 'prev) '())) -(define (next-counter counter) (apply (counter 'next) '())) - -; Create a 'counter' -(define *counter* (make-counter 42)) - -; Use the 'counter' ... -(print (prev-counter *counter*)) -(print (prev-counter *counter*)) -(print (next-counter *counter*)) - -;; @@PLEAC@@_10.4 -; Scheme interpreters generally provide a rich collection of procedure -; metadata, as well as easy access to a program's current 'execution -; state'. Put simply, provision of a powerful, highly customisable -; debugging / tracing facility is almost taken for granted. However, using -; it to perform as trivial a task as obtaining the current function name -; is less than trivial [at least it seems so in Guile] as it appears to -; require quite some setup work. Additionally, the documentation talks -; about facilities e.g. trap installation, that don't appear to be -; available [at least, I couldn't find them]. -; -; Example below uses in-built debugging facilities to dump a backtrace -; to a string port and extract the caller's name from the resulting -; string. Not exactly elegant ... - -; Execute using: guile --debug ... else no useful output seen -(use-modules (ice-9 debug)) - -(define (child num) - ; Create stack [i.e. activation record] object, discarding - ; irrelevant frames - (let ((s (make-stack #t 3 1)) - (trace-string-port (open-output-string)) - (parent-name "")) - - ; Dump backtrace to string port - (display-backtrace s trace-string-port) - - ; Extract caller's name from backtrace data - ; [shamefully crude - don't do this at home !] - (set! parent-name - (caddr (string-tokenize - (cadr (string-split - (get-output-string trace-string-port) - #\newline)) - char-set:graphic))) - - ; Who's your daddy ? - (print parent-name))) - -; Each invocation of 'child' should see 'parent' displayed as -; the caller -(define (parent) - (child 1) - (child 2) - (child 3)) - -(parent) - -;; @@PLEAC@@_10.5 -; Procedure parameters are references to entities, so there is no special -; treatment required. If an argument represents a mutable object such -; as an array, then care should be taken to not mutate the object within -; the procedure, or a copy of the object be made and used - -(array-diff *array1* *array2*) - -;; ------------ - -(define (add-vector-pair x y) - (let* ((vector-length (vector-length x)) - (new-vec (make-vector vector-length))) - (let loop ((i 0)) - (cond - ((= i vector-length) new-vec) - (else - (vector-set! new-vec i (+ (vector-ref x i) (vector-ref y i))) - (loop (+ i 1)) ))) )) - -;; ---- - -(define *a* '#(1 2)) -(define *b* '#(5 8)) - -(define *c* (add-vector-pair *a* *b*)) - -(print *c*) - -;; ---- - - ... - - (if (and (vector? a1) (vector? a2)) - (print (add-vector-pair a1 a2)) - ;else - (print "usage: add-vector-pair a1 a2")) - - ... - -;; @@PLEAC@@_10.6 -; AFAIK there is no Scheme equivalent to Perl's 'return context' where -; it is possible to use language primitives [e.g. 'wantarray'] to -; dynamically specify the return type of a procedure. It is, however, -; possible to: -; * Return one of several types from a procedure, whether based on -; processing results [e.g. 'false' on error, numeric on success], or -; perhaps specified via control argument -; * Check procedure return type and take appropriate action - -(define (my-sub) - (let* ((datatype (vector '() 7 '(1 2 3) "abc" 'sym))) - (vector-ref datatype (random (vector-length datatype))) )) - -;; ---- - -; '*result*' is bound to a randomly chosen datatype -(define *result* (my-sub)) - -(cond - ; It is common to return an empty list to represent 'void' - ((null? *result*) (print "void context")) - - ((list? *result*) (print "list context")) - ((number? *result*) (print "scalar context")) - ((string? *result*) (print "string context")) - ((symbol? *result*) (print "atom context")) - (else (print "Unknown type"))) - -;; @@PLEAC@@_10.7 -; Keyword parameters are fully supported. Note that pairs have -; replaced Perl strings in the examples since they are easier to -; manipulate - -(use-modules (ice-9 optargs)) - -(define* (the-func #:key (increment (cons 10 's)) - (finish (cons 0 'm)) - (start (cons 0 'm))) - (print increment) - (print finish) - (print start)) - -(the-func) -(the-func #:increment (cons 20 's) #:start (cons 5 'm) #:finish (cons 30 'm)) -(the-func #:start (cons 5 'm) #:finish (cons 30 'm)) -(the-func #:finish (cons 30 'm)) -(the-func #:start (cons 5 'm) #:increment (cons 20 's)) - -;; @@PLEAC@@_10.8 -;; @@INCOMPLETE@@ -;; @@INCOMPLETE@@ - -;; @@PLEAC@@_10.9 -; The return of multiple values, whether arrays or other items, may be -; achieved via: -; * Packaging return items as a single list, structure or array, an -; approach which is usable across many languages, though can be -; clunky because the procedure caller must manually extract all -; items -; * The 'values' procedure, a more Schemish idiom, is usually used in -; conjunction with the 'call-with-values' procedure [the former combines -; multiple values, the latter captures and cleanly extracts them]. It -; comes into its own, however, when used to create a 'macro' [an -; extension to the Scheme language] like 'let-values', a variation of -; the 'let' form that allows multiple return values to be placed directly -; into separate variables. Implementation shown here is from 'The -; Scheme Programming Language, 3rd Edition' by R. Kent Dybvig, though -; there exists a more standard implementation in SRFI-11. There is also -; the 'receive' functionality accessable via: (use-modules (ice-9 receive)) - -; [1] Implementation of 'somefunc' returning muliple values via packaging -; items within a list that is returned -(define (somefunc) - (let ((a (make-vector 5)) - (h (make-hash-table 5))) - (list a h) )) - -; Retrieving procedure values requires that the return list be captured -; and each contained item separately extracted ['let*' used in place of -; 'let' to ensure correct retrieval order] -(let* ((return-list (somefunc)) - (a (car return-list)) - (b (cadr return-list))) - - ... do something with 'a' and 'b' ...) - -;; ---------------------------- - -; [2] Implementation of 'somefunc' returning muliple values using the -; 'values' procedure - -(use-syntax (ice-9 syncase)) - -; 'let-values' from: http://www.scheme.com/tspl3/syntax.html#fullletvalues -(define-syntax let-values - (syntax-rules () - ((_ () f1 f2 ...) (let () f1 f2 ...)) - ((_ ((fmls1 expr1) (fmls2 expr2) ...) f1 f2 ...) - (lvhelp fmls1 () () expr1 ((fmls2 expr2) ...) (f1 f2 ...))))) - -(define-syntax lvhelp - (syntax-rules () - ((_ (x1 . fmls) (x ...) (t ...) e m b) - (lvhelp fmls (x ... x1) (t ... tmp) e m b)) - ((_ () (x ...) (t ...) e m b) - (call-with-values - (lambda () e) - (lambda (t ...) - (let-values m (let ((x t) ...) . b))))) - ((_ xr (x ...) (t ...) e m b) - (call-with-values - (lambda () e) - (lambda (t ... . tmpr) - (let-values m (let ((x t) ... (xr tmpr)) . b))))))) - -;; ------------ - -(define (somefunc) - (let ((a (make-vector 5)) - (h (make-hash-table 5))) - (values a h) )) - -; Multiple return items placed directly into separate variables -(let-values ( ((a h) (somefunc)) ) - (print (array? a)) - (print (hash-table? h))) - -;; @@PLEAC@@_10.10 -; Like most modern languages, Scheme supports exceptions for handling -; failure, something that will be illustrated in another section. However, -; conventions exist as to the choice of value used to indicate failure: -; * Empty list i.e. '() is often used for this task, as is it's string -; counterpart, "", the empty string -; * Return false i.e. #f to indicate failed / not found etc, and a valid -; value otherwise [e.g. testing set membership: if not a member, return -; #f, but if a member, return the item itself rather than #t] - -; Return empty list as indicating 'failure' -(define (sub-failed) '()) - -;; ------------ - -(define (look-for-something) - ... - (if (something-found) - ; Item found, return the item - something - ;else - ; Not found, indicate failure - #f - )) - -;; ---- - -(if (not (look-for-something)) - (print "Item could not be found ...") -;else - ; do something with item ... - ... - -;; ------------ - -; An interesting variation on returning #f as a failure indicator is -; in using the, 'false-if-exception' procedure whereby a procedure is -; executed, any exceptions it may throw caught, and handled by simply -; returning #f. See example in section on Exception Handling below. - -;; ------------ - -(define (ioctl) ... #f) - -(or (ioctl) (begin (print "can't ioctl") (exit 1))) - -;; @@PLEAC@@_10.11 -; Whether Scheme is seen to support prototyping depends on the definition -; of this term used: -; * Prototyping along the lines used in Ada, Modula X, and even C / C++, -; in which a procedure's interface is declared separately from its -; implementation, is *not* supported -; * Prototyping in which, as part of the procedure definition, parameter -; information must be supplied. This is a requirement in Scheme in that -; parameter number and names must be given, though there is no need to -; supply type information [optional and keyword parameters muddy the -; waters somewhat, but the general principle applies] - -(define (func-with-no-arg) ...) -(define (func-with-one-arg arg1) ...) -(define (func-with-two-arg arg1 arg2) ...) -(define (func-with-three-arg arg1 arg2 arg3) ...) - -;; @@PLEAC@@_10.12 -; Not exactly like the Perl example, but a way of immediately -; exiting from an application -(define (die msg . error-code) - (display (string-append msg "\n") (current-error-port)) - (exit (if (null? error-code) 1 (car error-code)))) - -;; ---- - -(die "some message") - -;; ------------ - -; An exception is thrown via 'throw'; argument must be a symbol -(throw 'some-exception) - -; Invalid attempts - these, themselves force a 'wrong-type-arg -; exception to be thrown -(throw #t) -(throw "my message") -(throw 1) - -;; ------------ - -; Example of a 'catch all' handler - 'proc' is executed, and any -; exception thrown is handled, in this case by simply returning false -(define (false-if-exception proc) - (catch #t - proc - (lambda (key . args) #f))) - -(define (func) - (print "Starting 'func' ...") - (throw 'myexception 1) - (print "Leaving 'func' ...")) - -;; ---- - -(if (not (false-if-exception main)) - (print "'func' raised an exception") - (print "'func' executed normally")) - -;; ------------ - -; More typical exception handling example in which: -; * 'func' is executed -; * 'catch' either: -; - returns return value of 'func' [if successful] -; - executes handler(s) - -(define (full-moon-exception-handler key . args) - (print "I'm executing after stack unwound !")) - -(define (full-moon-exception-prewind-handler key . args) - (print "I'm executing with the stack still intact !")) - -(define (func) - (print "Starting 'func' ...") - (throw 'full-moon-exception 1) - (print "Leaving 'func' ...")) - -(catch 'full-moon-exception - func - full-moon-exception-handler - full-moon-exception-prewind-handler) - -;; @@PLEAC@@_10.13 -; Scheme is lexically-scoped, so same-name, higher-level variables -; are merely shadowed in lower-level blocks. Upon exit from those -; blocks the higher-level values are again available. Therefore, the -; saving of global variables, as required by Perl, is not necessary - -; Global variable -(define age 18) - -; Procedure definition creates a closure - it captures the earlier -; version of, age', and will retain it -(define (func) - (print age)) - -(if (condition) - ; New 'local' variable created which acts to shadow the global - ; version - (let ((age 23)) - - ; Prints 23 because the global variable is shadowed within - ; this block - (print age) - - ; However, lexical-scoping ensures 'func' still accesses the - ; 'age' which was active when it was defined - (func) )) - -; The use of 'fluid-let' allows for similar behaviour to Perl's i.e. -; it mimics dynamic scope, but it does so cleanly in that once its -; scope ends any affected global variables are restored to previous -; values -(if (condition) - - ; This does not create a new 'local' variables but temporarily - ; sets the global variable, 'age' to 23 - (fluid-let ((age 23)) - - ; Prints 23 because it is accessing the global version of 'age' - (print age) - - ; Prints 23 because it is its lexically-scoped version of 'age' - ; that has its value altered, albeit temporarily - (func) )) - -;; @@PLEAC@@_10.14 -; Define two procedures, bind them to identifiers -(define (grow) (print "grow")) -(define (shrink) (print "shrink")) - -; Separate procedures executed -(grow) -(shrink) - -; Rebind identifier; now acts as alias for latter -(define grow shrink) - -; Same procedure executed in both cases -(grow) -(shrink) - -;; ------------ - -; As for previous except that rebinding is localised and -; ends once local scope exited -(let ((grow shrink)) - (grow) - (shrink)) - -;; ---------------------------- - -; Example of dynamically creating [from text data] and binding -; procedures. The example here is conceptually similar to the Perl -; example in that it makes use of an 'eval' type of facility to -; generate code from text. In Scheme such tasks are generally better -; dealt with using macros - -; List of procedure name / first argument pairs -(define *colours* - (list - '("red" . "baron") - '("blue" . "zephyr") - '("green" . "beret") - '("yellow" . "ribbon") - '("orange" . "county") - '("purple" . "haze") - '("violet" . "temper") )) - -; Build a series of procedures dynamically by traversing the -; *colours* list and obtaining: -; * Procedure name from first item of pair -; * Procedure argument from second item of pair -(for-each - (lambda (colour) - (let ((proc-string - (string-append - "(define " (car colour) " (lambda () " - "\"<FONT COLOR=" (car colour) ">" (cdr colour) - "</FONT>\"))" ))) - (eval-string proc-string))) - *colours*) - -; Apply each of the dynamically-built procedures -(for-each - (lambda (colour) - (print (apply (string->procedure (car colour)) '()))) - *colours*) - -;; @@PLEAC@@_10.15 -; AFAICT Guile doesn't implement an AUTOLOAD facility in which a -; 'replacement' function is available should another one fail to -; load [though there is an autoload feature available with modules -; which is a load-on-demand facility aimed at conserving memory and -; speeding up initial program load time]. -; -; One might think it would be feasable, however, to use exception -; handling to provide roughly similar functionality: - -; Catch all exceptions -(catch #t - ; Undefined procedure, 'x' - x - ; Exception handler could load missing code ? - (lambda (key . args) ... )) - -; However, an undefined function call is reported as: -; -; ERROR: Unbound variable: ... -; -; and this situation doesn't appear to be user-trappable. -; - -;; @@PLEAC@@_10.16 -; Both implementations below are correct, and exhibit identical -; behaviour - -(define (outer arg) - (let* ((x (+ arg 35)) - (inner (lambda () (* x 19)))) - (+ x (inner)))) - -;; ---------------------------- - -(define (outer arg) - (let ((x (+ arg 35))) - (define (inner) (* x 19)) - (+ x (inner)))) - -;; @@PLEAC@@_10.17 -;; @@INCOMPLETE@@ -;; @@INCOMPLETE@@ - -;; @@PLEAC@@_13.0 -;; Guile OOP is in the (oop goops) module (based on CLOS). All -;; following sections assume you have (oop goops loaded). -(use-modules (oop goops)) -(define-class <data-encoder> ()) -(define obj (make <data-encoder>)) - -(define obj #(3 5)) -(format #t "~A ~A\n" (class-of obj) (array-ref obj 1)) -(change-class v <human-cannibal>) ; has to be defined -(format #t "~A ~A\n" (slot-ref obj stomach) (slot-ref obj name)) - -(slot-ref obj 'stomach) -(slot-set! obj 'stomach "Empty") -(name obj) -(set! (name obj) "Thag") - -;; inheritance -(define-class <lawyer> (<human-cannibal>)) - -(define lector (make <human-cannibal>)) -(feed lector "Zak") -(move lector "New York") - -;; @@PLEAC@@_13.1 -(define-class <my-class> () - (start #:init-form (current-time)) - (age #:init-value 0)) - -;; classes must have predefined slots, but you could use one as a -;; dictionary: -(define-class <my-class> () - (start #:init-form (current-time)) - (age #:init-value 0) - (properties #:init-value '())) -(define (initialize (m <my-class>) initargs) - (and-let* ((extra (memq #:extra initargs))) - (slot-set! m 'properties (cdr extra)))) - -;; @@PLEAC@@_13.2 -;; For smobs (external C objects), you can specify a callback to be -;; performed when the object is garbage collected with the C API -;; function `scm_set_smob_free'. This solves the problem of cleaning up -;; after external objects and connections. Guile doesn't use reference -;; count garbage collection, so circular data structures aren't a -;; problem. - -;; @@PLEAC@@_13.3 -;; either use slot-ref/set! -(slot-ref obj 'name) -(slot-set! obj 'name value) - -;; or define the class with accessors -(define-class <my-class> () - (name #:accessor name)) -(name obj) -(set! (name obj) value) - -;; or use getters/setters to implement read/write-only slots -(define-class <my-class> () - (name #:getter name) - (age #:setter age)) -(name obj) -(set! (age obj) value) - -;; or implement getters/setters manually -(define-method ((setter name) (obj <my-class>) value) - (cond ((string-match "[^-\\w0-9']" value) - (warn "funny characters in name")) - ((string-match "[0-9]" value) - (warn "numbers in name")) - ((not (string-match "\\w+\\W+\\w+" value)) - (warn "prefer multiword names")) - ((not (string-match "\\w" value)) - (warn "name is blank"))) - (slot-set! obj 'name (string-downcase value))) - -;; @@PLEAC@@_13.4 -;; override the initialize method -(define body-count 0) - -(define-method (initialize (obj <person>) initargs) - (set! body-count (1+ body-count)) - (next-method)) - -(define people '()) -(do ((i 1 (1+ i))) - ((> i 10)) - (set! people (cons (make <person>) people))) - -(format #t "There are ~A people alive.\n" body-count) - -(define him (make <person>)) -(slot-set! him 'gender "male") - -(define her (make <person>)) -(slot-set! her 'gender "female") - -;; use the :class allocation method -(slot-set! (make <fixed-array>) 'max-bounds 100) ; set for whole class -(define alpha (make <fixed-array>)) -(format #t "Bound on alpha is ~D\n" (slot-ref alpha 'max-bounds)) -;; 100 - -(define beta (make <fixed-array>)) -(slot-set! beta 'max-bounds 50) ; still sets for whole class -(format #t "Bound on alpha is ~D\n" (slot-ref alpha 'max-bounds)) -;; 50 - -;; defined simply as -(define-class <fixed-array> () - (max-bounds #:init-value 7 #:allocation #:class)) - -;; @@PLEAC@@_13.5 -;; Guile classes are basically structs by definition. If you don't care -;; about OO programming at all, you can use records, which are portable -;; across most Schemes. This is, however, an OO chapter so I'll stick -;; to classes. -(define-class <person> () name age peers) - -(define p (make <person>)) -(slot-set! p 'name "Jason Smythe") -(slot-set! p 'age 13) -(slot-set! p 'peers '("Wilbur" "Ralph" "Fred")) -(format #t "At age ~D, ~A's first friend is ~A.\n" - (slot-ref p 'age) (slot-ref p 'name) (car (slot-ref p 'peers))) - -;; For type-checking and field validation, define the setters -;; accordingly. -(define-class <person> () - (name #:accessor name) - (age #:accessor age)) - -(define-method ((setter age) (p <person>) a) - (cond ((not (number? a)) - (warn "age" a "isn't numeric")) - ((> a 150) - (warn "age" a "is unreasonable"))) - (slot-set! p 'age a)) - -(define-class <family> () - (head #:init-form (make <person>) #:accessor head) - (address #:init-value "" #:accessor address) - (members #:init-value '() #:accessor members)) - -(define folks (make <family>)) - -(define dad (head folks)) -(set! (name dad) "John") -(set! (age dad) 34) - -(format #t "~A's age is ~D\n" (name dad) (age dad)) - -;; Macros are the usual way to add syntactic sugar - -;; For all fields of the same type, let's use _ to mean the slot name in -;; the options expansion. -(define-macro (define-uniform-class name supers slots . options) - `(define-class ,name ,supers - ,@(map (lambda (s) (cons s (map (lambda (o) (if (eq? o '_) s o)) options))) - slots))) - -(define-uniform-class <card> (name color cost type release text) - #:accessor _ #:init-value "") - -;; If you *really* wanted to enforce slot types you could use something -;; like the above with the custom setter. To illustrate reversing -;; normal slot definition args, we'll reverse an init-value: -(define-macro (define-default-class name supers . default&slots) - `(define-class ,name ,supers - ,@(map (lambda (d&s) (list (cadr d&s) - #:init-value (car d&s) - #:accessor (cadr d&s))) - default&slots))) - -(define-default-class hostent () - ("" name) - ('() aliases) - ("" addrtype) - (0 length) - ('() addr-list)) - -;; Nothing special needed for Aliases - all names are equal -(define type addrtype) -(define-method (addr (h <hostent>)) - (car (addr-list h))) - -;; @@PLEAC@@_13.6 -;; A little more clear than the Perl, but not very useful. -(define obj1 (make <some-class>)) -(define obj2 (make (class-of obj1))) - -;; Use the shallow-clone or deep-clone methods to initialize from -;; another instance. -(define obj1 (make <widget>)) -(define obj2 (deep-clone obj1)) - -;; @@PLEAC@@_13.7 -;; Use eval or a variant to convert from a symbol or string to the -;; actual method. As shown in 13.5 above, methods are first class and -;; you'd be more likely to store the actual method than the name in a -;; real Scheme program. -(define methname "flicker") -(apply-generic (eval-string methname) obj 10) - -(for-each (lambda (m) (apply-generic obj (eval-string m))) - '("start" "run" "stop")) - -;; really, don't do this... -(define methods '("name" "rank" "serno")) -(define his-info - (map (lambda (m) (cons m (apply-generic (eval-string m) obj))) - methods)) - -;; same as this: -(define his-info (list (cons "name" (name obj)) - (cons "rank" (rank obj)) - (cons "serno" (serno obj)))) - -;; a closure works -(define fnref (lambda args (method obj args))) -(fnref 10 "fred") -(method obj 10 fred) - -;; @@PLEAC@@_13.8 -;; use is-a? -(is-a? obj <http-message>) -(is-a? <http-response> <http-message>)
\ No newline at end of file |