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 ...)

(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 ...)
[syntax] (foreach/* (((A B ...) LIST) ((C D ...) LIST2) ...) body ...)
(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 ...
(combine 'q 245 "cat") => "q245cat"
[procedure] combine/w OBJ ...
(combine/w 'q 245 "cat") => "q245\"cat\""
[procedure] combine/sym OBJ ...
(combine/sym 'q 245 "cat") => 'q245cat
[procedure] combinesp OBJ ...
(combinesp 'q 245 "cat") => "q 245 cat"
[procedure] combinesp/w OBJ ...
(combinesp/w 'q 245 "cat") => "q 245 \"cat\""
[procedure] combinesp/sym OBJ ...
(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>
(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>
[procedure] print0nl ITEM ...
[procedure] printsp ITEM ...
[procedure] printsp0 ITEM ...

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 ...
[procedure] write0 ITEM ...
[procedure] write0nl ITEM ...
[procedure] writesp ITEM ...
[procedure] writesp0 ITEM ...
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
(sym/str 'kettle) => "kettle"
(sym/str skillet:) => "skillet"
(sym/str 1762) => "1762"
(sym/str "String") => "String"
   
[procedure] str/sym ITEM
(str/sym "kettle") => 'kettle
(str/sym 1762) => |1762|
[procedure] sym/str->num ITEM NOTRIM?
(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
[syntax] symls->strls LIST
[syntax] strls->numls LIST

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:

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
[syntax] alist/r+ K V ALIST
[syntax] alist/r! K V ALIST
[syntax] alist/a+ K V ALIST
[syntax] alist/a! K V ALIST
[procedure] alist/d+ K ALIST
[syntax] alist/d! K ALIST

alist query

[procedure] k->v KEY ALIST
[procedure] k->vci KEY ALIST

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
(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

string indexing

[procedure] str-ndx-ls STR CHR STARTAT FROMRIGHT?
(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?
[procedure] str-right-ndx STR CH
[procedure] str-left-ndx STR CH
[procedure] strip-extension FILENAME
[procedure] get-extension FILENAME

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 !< !>=
[procedure] qsort-str 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
(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
[procedure] strim STR OPTIONS
(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:
[procedure] split-str-at-char STR SCHARS KEEP
(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
(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
[procedure] rad2deg RAD
[syntax] mkconst VAR VAL
[procedure] PI
(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