#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. |# ; See full racket-documentation for these functions/keywords: ; (provide (all-defined-out) ; Let students use contracts, in intermediate-and-above languages! -> define/contract (rename-out [define-struct/c define-struct/contract]) and/c or/c not/c one-of/c listof module module+ module* ; As convenience for less typing, ; we also provide the shorter names "define/c", "define-struct/c" below. ; For convenience, a shorter name than "check-expect": (rename-out [check-expect c-e] [check-within c-w]) struct ; example below. 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: provide require all-defined-out only-in rename-in rename-out module+ ; obvious functions that maybe should've been in Intermediate-Student lang: 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 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 hash-set ; add a key/value pair (functional -- returns a *new* hash-table) hash? ; 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 ; a list of key/value cons-pairs, e.g. '(("en" . "hi") ("fr" . "bonjour") ("de" . "tag")) hash-keys ; a list of the keys (equivalent to `(map first (hash->list ...))` ) hash-values ; a list of the values (equivalent to `(map rest (hash->list ...))` ) 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 student languages don't allow `only-in`, ; and requiring the entire library gives a conflict.) ; (require (only-in racket/gui play-sound)) (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`. (define (play-sound-then snd expr) (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-in-range 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-in-range 0 +inf.0)) (define natural? non-negative-integer?) (define integer>=0? non-negative-integer?) ; for consistency to `real>=0?` (define (non-empty-string? val) (and (string? val) (not (string=? "" val)))) ; useful contract-generators: ; `(real-in-range 3 7)` returns a *function*, ; so: ((real-in-range 3 7) 5) is #true, ; and a signature can be something like: ; (-> (real-in-range 0 1) string? boolean?) (define/contract (real-in-range 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)))) ; 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?) ; a better name (imo): (define non not/c) ; 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)))) (define/contract (empty-string? s) (-> any/c boolean?) (and (string? s) (string=? s ""))) (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) (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) ) #| ; `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 v2.05~n") ; ; 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` #| @license CC0 -- public domain. You may freely copy, modify, or distribute this code, without restriction. https://creativecommons.org/publicdomain/zero/1.0/ |#