You are looking at historical revision 44919 of this page. It may differ significantly from its current revision.
nutils
Description
nutils is an eclectic collection of easy-to-use and versatile utilities. Unquestionably the basic ideas underlying these utilities have been expressed in Scheme many times over the decades, but nonetheless the versions found here offer a new twist or enhancements to old ideas. It's hoped these variations provide convenience and efficiency, thus make Scheme programming just a bit more productive and enjoyable.
Author
Jules Altfas
Repository
https://codeberg.org/jrapdx/nutils
Dependencies
nutils api
let-kw
let-kw is somewhat like let-optionals and similar (chicken base), but with additional features that make it more useful.
<syntax>(let-kw REST ((KEYWORD/SYMBOL/STRING VARIABLE DEFAULT) ...) BODY ...)
- in - REST - a list, generally a dotted rest-list, containing arguments to parse
- in - KEYWORD/SYMBOL/STRING - the argument/value that identifies the intended input
- in - VARIABLE - an identifier naming the generated variable
- in - DEFAULT - value assigned to the variable when K/S/S not an input.
- in - BODY - like body of a let. Created variables are visible throughout body
- returns: value is result of last expression in body.
(import scheme.base nutils) (define (myproc . rest) (let-kw rest ((k0: k0 "cat")) (printnl "Animal:" k0))) ;; (myproc k0: "dog") => Animal: dog ;; (myproc) => Animal: cat
As noted, the "key" can be almost any scheme object:
(import ...) (define (proc2 . rest) (let-kw rest (('-animal k0 "cat")) (printnl "Animal:" k0))) ;; (proc2 '-animal "dog") => Animal: dog
let-kw has another capability: it can be invoked with only a variable and default value. In this case it behaves like let-optional:
(import ...) (define (proc-opt . rest) (let-kw rest ((k0 "cat")) (printnl "Animal:" k0))) ;; (proc-opt "dog") => Animal: dog ;; (proc-opt) => Animal: cat
Where it differs from let-optional is that both keyed and unkeyed inputs can be used together with interesting results:
(import ...) (define (two-in-one . rest) (let-kw rest ((keyed: keyed 'big-value)) (let-kw rest ((xxx 0) (aaa 101)) (printnl keyed xxx aaa)))) (two-in-one keyed: 'big-money 2578) ;; => big-money 2578 101 (two-in-one 2578 keyed: 'big-money) ;; => big-money 2578 101 (two-in-one 2578) ;; => big-value 2578 101 (two-in-one) ;; big-value 0 101 (two-in-one tiger: 'home) => big-value tiger: home
Keyed arguments are removed from rest list, when it's passed to the second let-kw it no longer contains the keyed member and following value. So then let-kw matches the args in rest. If there are fewer args in rest than variables in let-kw, default values are assigned to the variables. This works because keyed args can't interfere with the "ordinary" optionals. There's no "rest confusion" due to DSSSL not removing keyword args from the rest list.
Note that let-kw can be used without keyed args at all. Pure optional let-kw works like optionals/let-optionals are expected to. When using keyed and unkeyed binidng lists, the keyed lists must come first as illustrated above. Also keyed and unkeyed should not be mixed in a bindings list.
foreach and foreach*
These are variations on for-each and map with similar basic semantics. However, foreach and foreach* invert, simplify and extend the syntax to allow accessing multiple elements of input lists at the same time.
foreach and foreach* have identical syntax except foreach* returns a list while foreach does not. In this document foreach/* refers to information that applies to both.
[syntax] (foreach/* ((VAR LIST) (VAR2 LIST2) ...) body ...)- in - LIST ... - basic syntax. VAR, VAR2, etc., are variables visible in BODY. Each VAR holds one element of the respective list.
- returns: foreach - no value, foreach* list of VAR, VAR2, etc., as determined by BODY expressions. If BODY result is the symbol _##_ an element is not added to output list.
- in - LIST ... - variables A B ..., C D ... hold one element of the respective list. IOW any number of elements can be extracted from lists at one time. The only restriction is that the same number of elements is taken from each list. Processing stops when shortest list runs out of elements. In foreach*, the result of BODY expressions is appended to output list, except if that value is the symbol _##_.
- returns: as above.
(import scheme.base nutils) (define op (open-output-file "demo1.txt")) (define row-vals '(123 282 366 471 543 612 798 882 936)) (foreach ((v row-vals)) (printnl v "divided by 3 is" (/ v 3) prn: op)) (close-output-port op) ;; demo1.txt contains rows: ;; 123 divided by 3 is 41 ;; 282 divided by 3 is 94 ;; ... (import scheme.base nutils srfi-1) (define critrs '("lion" "tiger" "elephant" "gorilla" "dingo" "hippo")) (let ((res (foreach* (((a b c) (iota 6)) ((x y z) critrs)) (let ((f0 (cons (combine/sym 'S #\- a) x)) (f1 (cons (combine/sym 'T #\- b) y)) (f2 (cons (combine/sym 'U #\- c) z))) (list f0 f1 f2))))) res) ;; res => (((S-0 . "lion") (T-1 . "tiger") (U-2 . "elephant")) ;; ((S-3 . "gorilla") (T-4 . "dingo") (U-5 . "hippo")))
combine
combine is similar to conc in the chicken.string module. That is, combine takes a number of objects, joining them into a string. What makes combine different is its 6 variations.
[procedure] combine OBJ ...- in - OBJ ... - any number of items: strings, symbols, characters, numbers, etc.,
- returns: all items as a single string without spaces between items.
(combine 'q 245 "cat") => "q245cat"[procedure] combine/w OBJ ...
- in - OBJ ... - items to combine
- returns: string with all items, preserves Scheme external representation.
(combine/w 'q 245 "cat") => "q245\"cat\""[procedure] combine/sym OBJ ...
- in - OBJ ... - items to combine
- returns: symbol with all items combined without spaces between.
(combine/sym 'q 245 "cat") => 'q245cat[procedure] combinesp OBJ ...
- in - OBJ ... - items to combine
- returns: string containing all items with a space between items.
(combinesp 'q 245 "cat") => "q 245 cat"[procedure] combinesp/w OBJ ...
- in - OBJ ... - items to combine
- returns: string with all items and space between. Preserves Scheme external representation.
(combinesp/w 'q 245 "cat") => "q 245 \"cat\""[procedure] combinesp/sym OBJ ...
- in - OBJ ... - items to combine
- returns: symbol with all items, space between.
(combinesp/sym 'q 245 "cat") => |q 245 cat|
printnl and variants
nutils provides procedures for terminal output with extended capabilites vs. the facilities of R7RS or chicken.io's print and print*.
[procedure] printnl ITEM ... prn: <output-port>- in - ITEM - virtually any scheme object and any number of ITEMs. These are printed to the terminal via display with a space between ITEMs and newline after the last ITEM is printed. (printnl = print + nl, or newline.)
- in - prn: <output-port> - by default, printnl (and all other print/write... procedures) print to current-output-port. However, the prn: keyword option directs printing to any open output port.
- returns: no value.
(import scheme.base nutils) (printnl 'test "my-output" 'isn\'t "bad at all!") => test my-output isn't bad at all! (define op (open-output-file "file1.txt")) ;; print to a file: (printnl "Here are test results:" prn: op) (printnl ... prn: op) ... (close-output-port op)[procedure] print0 ITEM ... prn: <output-port>
- in - ITEM - as above. print0 doesn't insert a space between ITEMs nor output newlinw after the last ITEM.
- in - prn: <output-port>
- returns: no value.
- in - ITEM - same as printnl. Except print0nl does NOT insert a space between items, but does print newline after the last ITEM.
- in - prn: <output-port>
- returns: no value.
- in - ITEM - as above. Inserts a space between items and after last item. newline is not printed after the last item. Useful for printing a list of items one or a few at a time.
- in - prn: <output-port>
- returns: no value.
- in - ITEM - as above. Like printsp except does NOT leave a space after the last ITEM is printed.
- in - prn: <output-port>
- returns: no value.
Each of the print... procedures has an analogous write... procedure. The difference between the series is that the latter uses write rather than display. Syntax of corresponding procedures is identical.
[procedure] writenl ITEM ...- in - ITEM ...
- returns: no value
- in - ITEM ...
- returns: no value
- in - ITEM ...
- returns: no value
- in - ITEM ...
- returns: no value
- in - ITEM ...
- returns: no value
Using the prn3port parameter
While the keyword argument prn: ... allows printing to a port, it can be tedious and error-prone when many invocations of print... are necessary, for example, writing a complex document to a file.
In such cases, using the prn3port parameter via parameterize makes a non-standard output port the default. Within the parameterize body, it's then unnecessary to use prn: ....
(import scheme.base nutils) (define op (open-output-file "complex-report-21.04.txt")) (parameterize ((prn3port op)) (printnl "Title:" article-title) (printnl "Author:" article-author) (printnl "Executive Summary:\n") (printnl summary-text) ... (printnl "References:" reference-list)) (close-output-port op)
string, symbol, number conversions
Conversion among strings, symbols and numbers is a common operation. These "shortcuts" make it simple to accomplish and reduce visual clutter:
[procedure] sym/str ITEM- in - ITEM may be a string, symbol, number, or keyword
- returns: a string, or #f if ITEM is none of the above.
(sym/str 'kettle) => "kettle" (sym/str skillet:) => "skillet" (sym/str 1762) => "1762" (sym/str "String") => "String"[procedure] str/sym ITEM
- in - ITEM - a string, symbol, number, or keyword
- returns: a symbol, or #f if ITEM is not one of the above.
(str/sym "kettle") => 'kettle (str/sym 1762) => |1762|[procedure] sym/str->num ITEM NOTRIM?
- in - ITEM is a numeric string, symbol or keyword
- in - NOTRIM: default is #f ("standard" trim "\n\t " is automatic).
- If NOTRIM is not #f, the default trim is not invoked. (By default, space, tab and newline chars at both ends of ITEM are removed).
- If other characters need to be trimmed, use (strim "string" "characters") to submit a string to sym/str->num.
- obj->num is an alias for sym/str->num.
- returns: a number or #f if ITEM does not convert to a number.
(sym/str->num "873") => 873 (sym/str->num '|873|) => 873 (sym/str->num #x873) => 2163 (sym/str->num "2163 ") => 2163 (sym/str->num " 2163 " #t) => #f (sym/str->num (strim "\t\t 2163--- \n" "-")) => 2163 (sym/str->num (strim '|\x9;\x9; 2163--- \xa;| "-")) => 2163[syntax] strls->symls LIST
- in - LIST of strings
- returns: list of symbols
- in - LIST of symbols, numbers, keywords
- returns: list of strings
- in - LIST of symbols, strings, numbers
- returns: list of numbers
Note: list conversions will insert #f when an element of the input list can't be converted to the output list type.
alist replace/append/delete, and alist query
The alist is commonly used as a database in Scheme programs for small data sets, such as configuration options or temporary data. Frequently modifying the data is called for, with 3 or 4 operations being used:
- Adding a pair to the alist.
- Deleting a pair from the alist.
- Replacing the data (cdr) of a pair.
- Appending data to the existing data of a pair/list.
The semantics of these operations can be surprising. Using set-cdr! on a pair extracted from an alist by assoc ... is visible in the alist. Preserving the original alist requires making modifications to a "deep copy" of the original, leaving the original unaltered.
alist/r_, alist/a_ and alist/d_ provide adding, replacing, appending and removing alist data. The "+" variants return a new alist, the "!" change the input alist.
[procedure] alist-dup ALIST- in - ALIST - the alist to duplicate
- returns: new alist identical to ALIST.
- in - K - the key, car, of a pair in the alist.
- in - V - the value, cdr, of the pair.
- in - ALIST
- alist/r+ replaces the value (cdr) of a pair with a new value, V. If the key, K, doesn't exist in any pair in the ALIST, a new K-V pair is added to the alist. In either case, a fresh ALIST is returned.
- returns: altered ALIST, original ALIST is unchanged.
- Destructive variant of alist/r+, sets ALIST to new ALIST.
- returns: modified input alist.
- in - K, V as above.
- in - ALIST
- ALIST is modified, V is appended to the pair's current value. If the key is not found in the ALIST, a new K-V pair is added to the ALIST.
- returns: the amended ALIST without changing the original.
- Destructive variant of alist/a+, setting ALIST to modified result.
- returns: altered original alist.
- in - K - key of pair to remove from ALIST.
- if K isn't found in ALIST, unchanged duplicate ALIST is returned.
- returns: altered alist, original is unmodified.
- in - K - key of pair to remove from ALIST.
- if K isn't found in ALIST, the unchanged ALIST is returned.
- returns: modified alist.
alist query
[procedure] k->v KEY ALIST- in - KEY - Performs case-sensitive search of ALIST for KEY, or car of pair.
- in - ALIST - ALIST to search.
- returns: value associated with KEY, or #f if key not found.
- in - KEY - Performs case-insensitive search of ALIST for KEY, or car of pair.
- in - ALIST - ALIST to search.
- returns: value associated with KEY, or #f if not found.
list-recv, pair-recv
list-recv is analogous to receive, except list-recv destructures a list instead of values. Most useful with procedures returning lists with small number of elements.
[syntax] list-recv (VAR1 ...) LIST BODY- in - VAR1 ... - number of variables must match length of LIST
- in - LIST - each element is assigned to variable in VARs.
- in - BODY - VAR1 ... are variables visible throughout BODY.
- returns: result of body expressions.
(import nutils) (define (myproc x y) (let ((a (* 101 x)) (b (* 202 y))) (list a b))) (list-recv (var-a var-b) (myproc 20 40) (printnl 'var-a 'is var-a) (printnl "var-b is" var-b)) ;; => var-a is 2020 ;; => var-b is 8080[syntax] pair-recv (K V) PAIR BODY
- in - K,V - K is assigned the key (car PAIR), V is value (cdr PAIR)
- in - PAIR - a key/value list, dotted pair, or procedure call returning a pair.
- in - BODY - variables K and V visible throughout BODY.
- returns: result of body expressions.
string indexing
[procedure] str-ndx-ls STR CHR STARTAT FROMRIGHT?- in - STR - search fro indexes of this string
- in - CHR - character to search for
- in - STARTAT - (optional) index to start from, default is 0.
- Note that with startat > 0, output indexes will not be different than 0 start. Applications can decrement the results by startat value if that is the desired semantics.
- in - FROMRIGHT? - (optional) if true, STARTAT and resulting indexes will count from end of string towards the beginning.
- returns: list of indexes that match input criteria or null list if none found.
(import nutils) (define str "abc dws qthcd po9inmhy ecvhn") (str-ndx-ls str #\c) ;; => (2 11 24) (str-ndx-ls str #\c 0 #t) ;; (3 16 25) (str-ndx-ls str #\c 5 #t) ;; (16 25) (foreach* ((n '(16 25))) (- n 5)) ;; => (11 20)[procedure] find-string-ndx STR CHR RT?
- in - STR - search for indexes of this string
- in - CHR - character to search for
- in - RT? - search from right (#t) or left (#f)
- returns: first index that satisfies criteria, or '() if none.
- in - STR - search for indexes of this string
- in - CH - character to search for
- returns: first rightmost index, or '() if none found.
- in - STR - search for indexes of this string
- in - CH - character to search for
- returns: first leftmost index, or '() if none found.
- in - FILENAME - a string
- returns: FILENAME without extension, or if no extension, input FILENAME.
- in - FILENAME - a string
- returns: extension, or if no extension, the empty string ("").
qsort
qsort is a simple, lightweight implementation of the qsort algorithm, useful for sorting short-moderate length lists encountered in many programs.
[procedure] qsort LIST #!optional !< !>=- in - LIST - the list to sort
- in - !< !>= - (optional) comparison procedures:
- Defaults: !< is <, !>= is >=. User-supplied compare procedures must accept data type of LIST.
- returns: sorted LIST.
- in - LIST - unsorted list of strings
- returns: sorted LIST.
sfmt
sfmt formats data in a manner analogous to "printf", the unix command line utility. sfmt takes a format string with familiar "%..." specifiers as used by "printf". sfmt is intended for simple use cases, more complex formatting is undoubtedly better served by extensions such as fmt, format, etc.
[procedure] sfmt FMT-STRING ITEMS- in - FMT-STRING - a string containing "%" specifiers as documented for the unix "printf" command.
- Specifiers recognized by sfmt include d, f, s, u, c, x, X and the qualifier ll. Precision, +/- components are also recognized. A "%" character can be inserted with "%%".
- sfmt checks if ITEMS match the types of corresponding specifiers in FMT-STRING. If a type mismatch is evident, an error message is printed to the terminal and an empty string returned.
- in - ITEMS - data to be formatted. Must match the types of specifiers. Number of specifiers in the format string must equal the number of ITEMS.
- returns: formatted string or "" on error.
(import scheme.base nutils) ... (define op (open-output-file "Report-doc.txt")) ;; print headers (printnl (sfmt "%-18s %-14s %-15s %-13s %s" "Title" "Author" "Pub. Date" "Dept." "Cost") prn: op ) (printnl (sfmt "%-18s %-14s %-15s %-13s %s" "------------" "-------" "----------" "------" "-------") prn: op) (printnl (sfmt "%-18s %-14s %-15s %-8d %12.2f" "Report No. 122" "Sam Jones" "2024-08-15" 1405 1223.57) prn: op) (close-output-port op) ;; file has this content: Title Author Pub. Date Dept. Cost ------------ ------- ---------- ------ ------- Report No. 122 Sam Jones 2024-08-15 1405 1223.57
misc
[syntax] while TEST BODY- in - TEST - a boolean expression
- in - BODY - BODY is evaluated until TEST is #f. The procedure break ... can be used to terminate the loop at any point.
- returns: value of BODY's last expression, or value input to break if it's used.
- in - STR - the string to trim
- in - OPTIONS:
- Chars to trim are contained in strings/objects. The default set is " \t\n".
- Keyword arguments include: L: <object>, R: <object> and D: <object>
- If L: <object> is given and not false, object's characters are added to default set and applied only to left side of STR.
- If R: <object> is given and not false, characters are added to default set and used to remove characters only from right side of STR.
- If D: <object> is supplied, its characters replace the default set affecting both ends of STR.
- If an optional argument is given, that is, a string/object without a keyword, its characters are added to the default set and used for character removal from both ends of STR.
- Non-string objects (symbols, numbers, lists, etc.) must be convertible to strings.
- returns: trimmed string
(import nutils) (define str "\t\t---... A string. ---+++\n\n") (strim str "-.+") ;; => "A string" (note 'dot' removed from right end) (strim str "-+" L: ".") ;; => "A string." (L: -> rm only from left end) (strim str "-+" L: '(\.)) ;; => "A string." (strim str "-+" L: '|.|) ;; => "A string."[procedure] sym/str-split SYM-STR SPLITTER: KEEP-NULLS: KEEP-SPLITTER:
- in - SYM-STR - the string or symbol to split. A symbol is converted to string.
- in - SPLITTER: - keyword, optional. The substring where the string will be split. Default is " ".
- in - KEEP-NULLS: - keyword, optional. On splitting, null strings may be formed and are ignored by default. Using KEEP-NULLS: #t will retain the null strings in the result.
- in - KEEP-SPLITTER: - keyword, optional. Ordinarily the "splitter" string is not part of the result. Using KEEP-SPLITTER: #t will keep the splitter string in the result.
- returns: list of strings divided by the SPLITTER: string or #f if splitting is not possible.
- in - STR - string to split
- in - SCHARS - string containing characters where string string should be split.
- in - KEEP - boolean, default #f. When #t, SCHARS are retained in result.
- returns: list of strings
(import nutils) (define str "xyzAbcdeabcdefghijAbcde") (split-str-at-char str "Ad") ;; => ("xyz" "bc" "eabc" "efghij" "bc" "e") (split-str-at-char str "Ad" #t) ;; => ("xyz" "Abc" "deabc" "defghij" "Abc" "de")[procedure] type-of ITEM
- in - ITEM - a scheme object
- returns: a symbol describing the type of the item, or 'unknown if the item's type can't be determined. type-of can identify most scheme objects. Number-vectors above u8 are identified as "number-vector".
(import nutils chicken.number-vector) (type-of 'cat) ; => 'symbol (type-of 22.22) ; => 'float (type-of 22) ; => 'exact-int (type-of #u8(0 0 0 0) ;; => 'bytevector (type-of #u32(0 0 0 0)) ;; => 'number-vector[procedure] deg2rad DEG
- in - DEG - angle (degrees)
- returns: angle as radians
- in - RAD - angle (radians)
- returns: angle as degrees
- in - VAR - the "variable", name of generated procedure
- in - VAL - constant value returned from generated procedure
- returns toplevel procedural constant (procedure of no arguments, returning VAL)
- generated by mkconst
- returns: value of PI, 3.14159265359
(import nutils) (print0nl "pi is approx " (PI)) ;; => pi is approx 3.14159265359
LICENSE
BSD-2
Versions
0.2.8 - Error fixes, added alist/d* 0.2.6 - Fixed tests 0.2.5 - Initial public release