#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: *also* 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: 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 (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)) ; play-sound-then : filename any/c -> any/c ; Play `snd`, and return whatever `expr` evaluates to. ; This is a hack for beginning-student, which doesn't have `begin`. (require (only-in racket/gui play-sound)) (define/contract (play-sound-then snd expr) (-> path-string? any/c any/c) (begin (play-sound snd #true) expr)) ; modulo/real : real (and real (non zero?)) . -> . real ; Like modulo, extended to real numbers (not just integers). ; (define (modulo/real x y) (* y (- (/ x y) (floor (/ x y))))) (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? (modulo/real 0 20) 0) (check-equal? (modulo/real 20 20) 0) (check-equal? (modulo/real 60 20) 0) (check-equal? (modulo/real -20 20) 0) (check-equal? (modulo/real -60 20) 0) (check-within (modulo/real 60.1 20) 0.1 0.001) (check-within (modulo/real 20.1 20) 0.1 0.001) (check-within (modulo/real 19.9 20) 19.9 0.001) (check-within (modulo/real +0.1 20) +0.1 0.001) (check-within (modulo/real -0.1 20) 19.9 0.001) (check-within (modulo/real 12.5 20) 12.5 0.001) (check-within (modulo/real 12.5 20) 12.5 0.001) (check-within (modulo/real 12.4 20) 12.4 0.001) (check-within (modulo/real 12.6 20) 12.6 0.001) ) ;;;;;;;;;;;;;;;;;; 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?`. ; 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 (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 ; 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 real>=0? non-negative-real?) (define real>? positive?) (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: (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) ;; 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)) ;(hotsoup-a (make-hotsoup 2 3 4 5 6)) ;error! ;(soup-e (make-hotsoup 2 3 4 5 6)) ;error (of course) (hotsoup-e (hotsoup 2 3 4 5 6)) |# (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.") )))) #| @license CC0 -- public domain. You may freely copy, modify, or distribute this code, without restriction. https://creativecommons.org/publicdomain/zero/1.0/ |#