#lang racket #| To use functions provided here from another file, put this file into the same directory, and in that file add `(require "student-extras.rkt")`. Note that you should *not* cut/paste this into a blank beginner-student tab; just download this file and put it into the same directory. To see *what* this file gives you: - to see what (non-student) racket functions are exported: peruse the the following `provide`; F1 will pull up documentation. - to see what handy but not-standard-racket functions are defined here: click on DrRacket's "(define …)" in top-left. |# (provide (all-defined-out) ; Anything defined in this file is exported. ; And now, re-export the following from full-racket: ; See full racket-documentation for these functions/keywords: ; Let students use contracts, in intermediate-and-above languages! -> define/contract ; NOTE: *also* provided as `define/c` below, for convenience. and/c or/c not/c one-of/c between/c listof non-empty-listof (rename-out [define-struct/c define-struct/contract]) ; NOTE: we're providing our *tweaked* version of racket's "define-struct/contract". ; NOTE: *still* provided as `define-struct/c` below, for convenience. ; For convenience, a shorter name than "check-expect": (rename-out [check-expect c-e] [check-within c-w]) struct ; Allows inheritance; see examples at end of file. struct-copy match-define ; In case a student wants to manage exceptions, provide: with-handlers exn? exn:fail? exn-message eprintf ; print to stderr (side-effect) ; In case students want to be making their own modules in Intermediate: ; (esp. helpful when they want to re-use a previous week's homework file.) module module+ module* provide require all-defined-out only-in except-in rename-in rename-out ; obvious functions that maybe should've been in Intermediate-Student lang: string-split string-titlecase degrees->radians radians->degrees ; These are ALREADY in Intermediate (hence commented-out), but are ; listed here because they're not in Beginner, ; if you want to modify this file: ; ; let ; let* ; letrec ; for-each ; string-upcase ; string-downcase ; Provide a bunch of the regexp functions. ; (There are more in racket/base; we only re-export the most common ones.) ; pregexp ; constructor, or use `#px"..."` as a literal-regexp. regexp? pregexp? regexp-match regexp-match* regexp-match? regexp-split regexp-replace regexp-replace* regexp-quote path-string? ; contract: (or/c path? string?), roughly file-exists? ; Provide a bunch of the hash functions. ; See https://docs.racket-lang.org/reference/hashtables.html?q=hash%20tables ; (There are more in racket/base; we only re-export the most common ones.) ; hash ; constructor hash-ref ; lookup by key. Can optionally provide a third arg: What to return if hash doesn't have the key. hash-set ; add a key/value pair (functional -- returns a *new* hash-table) hash? hash/c ; the following are much less common, but are handy in their narrow circumstance: make-immutable-hash ; constructor (variant) hash-count hash-empty? hash-has-key? hash-set* ; set many key/values "at once" hash-update ; change a value, eg `(hash-update word-counts word add1 0)` hash-remove hash->list ; list of key/value cons-pairs, e.g. '(("en" . "hi") ("fr" . "bonjour") ("de" . "tag")) hash-keys ; list of keys (same as `(map first (hash->list …))` but w/ `car` for `first` of a cons-pair) hash-values ; list of vals (same as `(map rest (hash->list …))` but w/ `cdr` for `rest` of a cons-pair) hash-map ; equivalent to `(map .. (hash->list ...))` ; higher-order-functions missing in interm-w-lambda: thunk const curry curryr ; re-exporting (though arguably too esoteric for teaching-lang): compose1 keyword-apply arity=? procedure-arity-includes? ) (require (only-in test-engine/racket-tests check-expect check-within)) ; The following two re-names would better be done via `(provide (rename-out [.. ..]))`, ; but we'll keep that list simpler. ; (define-syntax-rule (function args body) (λ args body)) (define-syntax-rule (fn args body) (λ args body)) (define-syntax-rule (define/c header contract body) (define/contract header contract body)) ; convenience: make a struct that is contracted, and #:transparent. (define-syntax-rule (define-struct/c struct-name fields+types) (define-struct/contract struct-name fields+types #:transparent)) ;;;;;;;;;;;;;;;;;; convenient type-predicates (for contracts) ;;;;;;;;;;;;;;;;;; (define any? any/c) ; something to put in our templates, where the contract's return-type goes: (define ...? any/c) ; useful contract-generators: ; `(in-range 3 7)` returns a *function*, ; so: ((in-range 3 7) 5) is #true, ; and a signature can be something like: ; (-> (in-range 0 1) string? boolean?) (define/contract (integer-between a b) (-> number? number? (-> any/c boolean?)) ; return the function: is a <= n < b ? (Note the half-open interval.) (λ(n) (and (integer? n) (<= a n) (< n b)))) ; a useful contract type: (define non-negative-integer? (integer-between 0 +inf.0)) (define natural? non-negative-integer?) (define integer>=0? non-negative-integer?) ; for consistency to `real>=0?` (define integer>0? (integer-between 1 +inf.0)) ; for consistency to `real>0?` (define positive-integer? integer>0?) ; for consistency to `positive?` ; `true?`, as a counterpart to `false?` (which is built-in). ; Note that in full-racket, non-booleans count as "tru-ish", ; even though they wouldn't satisfy `true?`. ; (define (true? val) (and (boolean? val) val)) (define (non-empty-string? val) (and (string? val) (not (string=? "" val)))) (define/contract (empty-string? s) (-> any/c boolean?) (and (string? s) (string=? s ""))) (define list-of listof) ; Allow "-" in the spellings. (define non not/c) ; A better name (imo). (define (non-empty-list? val) ; name parallels `non-empty-string?` (and (list? val) (not (equal? '() val)))) (define non-empty? non-empty-list?) ; name parallels `empty?` (module+ test ; This is a "submodule"; it is *not* run when this file is `require`d from elsewhere, ; but it *is* run if this file is run from DrRacket (or via `raco test` command-line). (require rackunit) (check-equal? (true? #true) #t) (check-equal? (true? #false) #f) (check-equal? (true? 17) #f) (check-equal? (true? "") #f) (check-equal? (true? '()) #f) (check-equal? (non-empty-string? 37) #false) (check-equal? (non-empty-string? "") #false) (check-equal? (non-empty-string? "a") #true) (check-equal? (non-empty-string? "howdy") #true) (check-equal? (empty-string? 37) #false) (check-equal? (empty-string? "") #true) (check-equal? (empty-string? "a") #false) (check-equal? (empty-string? "howdy") #false) ) ; useful contract-generators: ; `(real-between 3 7)` returns a *function*, ; so: ((real-between 3 7) 5) is #true, ; and a signature can be something like: ; (-> (real-between 0 1) string? boolean?) (define/contract (real-between a b) (-> real? real? (-> any/c boolean?)) ; return the function: is a <= x < b ? (λ(x) (and (real? x) (<= a x) (< x b)))) (define/contract (real-in-open a b) (-> real? real? (-> any/c boolean?)) ; return the function: is a < x < b ? (λ(x) (and (real? x) (< a x b)))) (define/contract (real-in-closed a b) (-> real? real? (-> any/c boolean?)) ; return the function: is a <= x <= b ? (λ(x) (and (real? x) (<= a x b)))) ; NOTE: this is called `between/c` in full-racket (module+ test (check-equal? ((real-between 5.2 98.6) 17.5) #t) (check-equal? ((real-between 5.2 98.6) 5.2) #t) (check-equal? ((real-between 5.2 98.6) 5.1999) #f) (check-equal? ((real-between 5.2 98.6) 5.2001) #t) (check-equal? ((real-between 5.2 98.6) 98.6) #f) (check-equal? ((real-between 5.2 98.6) 98.5999) #t) (check-equal? ((real-between 5.2 98.6) 98.6001) #f) (check-equal? ((real-between 5.2 98.6) -4) #f) (check-equal? ((real-in-open 5.2 98.6) 17.5) #t) (check-equal? ((real-in-open 5.2 98.6) 5.2) #f) (check-equal? ((real-in-open 5.2 98.6) 5.1999) #f) (check-equal? ((real-in-open 5.2 98.6) 5.2001) #t) (check-equal? ((real-in-open 5.2 98.6) 98.6) #f) (check-equal? ((real-in-open 5.2 98.6) 98.5999) #t) (check-equal? ((real-in-open 5.2 98.6) 98.6001) #f) (check-equal? ((real-in-open 5.2 98.6) -4) #f) (check-equal? ((real-in-closed 5.2 98.6) 17.5) #t) (check-equal? ((real-in-closed 5.2 98.6) 5.2) #t) (check-equal? ((real-in-closed 5.2 98.6) 5.1999) #f) (check-equal? ((real-in-closed 5.2 98.6) 5.2001) #t) (check-equal? ((real-in-closed 5.2 98.6) 98.6) #t) (check-equal? ((real-in-closed 5.2 98.6) 98.5999) #t) (check-equal? ((real-in-closed 5.2 98.6) 98.6001) #f) (check-equal? ((real-in-closed 5.2 98.6) -4) #f) ) ; a useful contract type; also exported as `real>=0?` (define (non-negative-real? x) ;(-> any/c boolean?) (and (number? x) (real? x) (<= 0 x))) (define (positive-real? x) ;(-> any/c boolean?) (and (number? x) (real? x) (positive? x))) (define real>=0? non-negative-real?) (define real>0? positive-real?) (define (finite-real? x) (and (real? x) (finite? x))) (module+ test (check-equal? (finite-real? 17) #t) (check-equal? (finite-real? #i17) #t) (check-equal? (finite-real? +inf.0) #f) (check-equal? (finite-real? -inf.0) #f) (check-equal? (finite-real? "chair") #f) ) ; N.B. takes in a `real?`, not `any/c`, to match racket's built-in `infinite?` (define/contract (finite? x) (-> real? boolean?) (and (not (infinite? x)) (not (nan? x)))) (module+ test (check-equal? (non-negative-real? "string") #false) (check-equal? (non-negative-real? 0) #true) (check-equal? (non-negative-real? 1) #true) (check-equal? (non-negative-real? 37) #true) (check-equal? (non-negative-real? #e3.7) #true) (check-equal? (non-negative-real? #i3.7) #true) (check-equal? (non-negative-real? -1) #false) (check-equal? (non-negative-real? -37) #false) (check-equal? (non-negative-real? #e-3.7) #false) (check-equal? (non-negative-real? #i-3.7) #false) (check-equal? (natural? "string") #false) (check-equal? (natural? 0) #true) (check-equal? (natural? 1) #true) (check-equal? (natural? 37) #true) (check-equal? (natural? #e3.7) #false) (check-equal? (natural? #i3.7) #false) (check-equal? (natural? -1) #false) (check-equal? (natural? -37) #false) (check-equal? (natural? #e-3.7) #false) (check-equal? (natural? #i-3.7) #false) ) #| ; `struct` is like `define-struct` but allows inheritance: ; When using this, include args `#:transparent` and possibly `#:consructor-name make-...` ). ; *** struct-inheritance requires intermediate-student or higher (not beginning-student, alas). ; ; Glitch: in student-level, (struct foo (...)) objects always print as `make-foo` even though ; the actual constructor-name is just `foo`. ; (Of course, if you want the `make-` constructor names, you can use `(define make-foo foo)` ; or even provide the keyword-arg #:constructor-name when defining the `struct`.) ; Usage example, of struct-inheritance: ; All soups have fields a,b; hotsoups extend soup with 3 more fields. (require "student-extras.rkt") (struct soup (a b) #:transparent) (define make-soup soup) ; We can bind the traditional name to the *actual* constructor. (soup 4 5) (make-soup 2 3) ; using the name `make-soup` that we `define`d above. ;; inherit: (struct hotsoup soup (c d e) #:transparent) ; inherit `soup`s fields, and add new ones c,d,e. (hotsoup 2 3 4 5 6) ; N.B. this prints as `(make-hotsoup ...)`, in student levels. (soup-a (hotsoup 2 3 4 5 6)) ; returns 2 (hotsoup-e (hotsoup 2 3 4 5 6)) ; returns 6 ;(hotsoup-a (make-hotsoup 2 3 4 5 6)) ;error, due to (lack of the) constructor-name (soup? (soup 2 3)) ; #t (hotsoup? (soup 2 3)) ; #f (soup? (hotsoup 2 3 4 5 6)) ; #t (hotsoup? (hotsoup 2 3 4 5 6)) ; #t |# (printf "using student-extras.rkt v~a~n" (first (last '((2.00 "version history:") (2.02 "made the provided `define-struct/c` #:transparent, just like `define-struct/contract'. (Note that this is different from full-racket's `define-struct/c`.)") (2.03 "re-export `struct-copy`, `curry` and a few others.") (2.04 "re-export `module+ listof or/c and/c one-of/c`") (2.05 "added `not/c`, `non`") (2.06 "re-export `non-empty-listof`; add `non-empty-list?` and its alias `non-empty?`") (2.07 "re-export `string-split`") (2.08 "export `true?`, as analagous to `false?`") (2.09 "re-export `between/c`. Rename `real-in-range` to `real-between` to jibe w/ racket's `between/c`.") (2.10 "export `list-of` as an alias of `listof`") (2.11 "export `hash/c`") (2.12 "re-export λ as `function`; provide `real>0? integer>0? positive-integer? path-string?`. Tweak bits of this file.") (2.13 "add positive-real? to go alongside real>0? (which had been mis-named `real>?`, oops)") (2.14 "play-with-sound ignores file-not-found exception (and, all others, oops). P.S. Should move to videogame-helpers?") (2.15 "removed two funcs: play-with-sound, modulo/real (both moved to videogame-helpers).") (2.16 "only changed comments: added clarifications to struct-inheritance examples.") (2.17 "test-cases for real-between & friends; also re-export λ as `fn` (a la rust)") )))) #| @license CC0 -- public domain. You may freely copy, modify, or distribute this code, without restriction. https://creativecommons.org/publicdomain/zero/1.0/ |#