;;; SRFI-1 list-processing library -*- Scheme -*- ;;; Reference implementation ;;; ;;; Copyright (c) 1998, 1999 by Olin Shivers. You may do as you please with ;;; this code as long as you do not remove this copyright notice or ;;; hold me liable for its use. Please send bug reports to shivers@ai.mit.edu. ;;; -Olin ;; Olin Shivers verified that he is fine with redistributing this code ;; under the LGPL. (Verified personally by Eli Barzilay.) ;;; This is a library of list- and pair-processing functions. I wrote it after ;;; carefully considering the functions provided by the libraries found in ;;; R4RS/R5RS Scheme, MIT Scheme, Gambit, RScheme, MzScheme, slib, Common ;;; Lisp, Bigloo, guile, T, APL and the SML standard basis. It is a pretty ;;; rich toolkit, providing a superset of the functionality found in any of ;;; the various Schemes I considered. ;;; This implementation is intended as a portable reference implementation ;;; for SRFI-1. See the porting notes below for more information. ;;; Exported: ;;; xcons tree-copy make-list list-tabulate cons* list-copy ;;; proper-list? circular-list? dotted-list? not-pair? null-list? list= ;;; circular-list length+ ;;; iota ;;; first second third fourth fifth sixth seventh eighth ninth tenth ;;; car+cdr ;;; take drop ;;; take-right drop-right ;;; take! drop-right! ;;; split-at split-at! ;;; last last-pair ;;; zip unzip1 unzip2 unzip3 unzip4 unzip5 ;;; count ;;; append! append-reverse append-reverse! concatenate concatenate! ;;; unfold fold pair-fold reduce ;;; unfold-right fold-right pair-fold-right reduce-right ;;; append-map append-map! map! pair-for-each filter-map map-in-order ;;; filter partition remove ;;; filter! partition! remove! ;;; find find-tail any every list-index ;;; take-while drop-while take-while! ;;; span break span! break! ;;; delete delete! ;;; alist-cons alist-copy ;;; delete-duplicates delete-duplicates! ;;; alist-delete alist-delete! ;;; reverse! ;;; lset<= lset= lset-adjoin ;;; lset-union lset-intersection lset-difference lset-xor lset-diff+intersection ;;; lset-union! lset-intersection! lset-difference! lset-xor! lset-diff+intersection! ;;; ;;; In principle, the following R4RS list- and pair-processing procedures ;;; are also part of this package's exports, although they are not defined ;;; in this file: ;;; Primitives: cons pair? null? car cdr set-car! set-cdr! ;;; Non-primitives: list length append reverse cadr ... cddddr list-ref ;;; memq memv assq assv ;;; (The non-primitives are defined in this file, but commented out.) ;;; ;;; These R4RS procedures have extended definitions in SRFI-1 and are defined ;;; in this file: ;;; map for-each member assoc ;;; ;;; The remaining two R4RS list-processing procedures are not included: ;;; list-tail (use drop) ;;; list? (use proper-list?) ;;; A note on recursion and iteration/reversal: ;;; Many iterative list-processing algorithms naturally compute the elements ;;; of the answer list in the wrong order (left-to-right or head-to-tail) from ;;; the order needed to cons them into the proper answer (right-to-left, or ;;; tail-then-head). One style or idiom of programming these algorithms, then, ;;; loops, consing up the elements in reverse order, then destructively ;;; reverses the list at the end of the loop. I do not do this. The natural ;;; and efficient way to code these algorithms is recursively. This trades off ;;; intermediate temporary list structure for intermediate temporary stack ;;; structure. In a stack-based system, this improves cache locality and ;;; lightens the load on the GC system. Don't stand on your head to iterate! ;;; Recurse, where natural. Multiple-value returns make this even more ;;; convenient, when the recursion/iteration has multiple state values. ;;; Porting: ;;; This is carefully tuned code; do not modify casually. ;;; - It is careful to share storage when possible; ;;; - Side-effecting code tries not to perform redundant writes. ;;; ;;; That said, a port of this library to a specific Scheme system might wish ;;; to tune this code to exploit particulars of the implementation. ;;; The single most important compiler-specific optimisation you could make ;;; to this library would be to add rewrite rules or transforms to: ;;; - transform applications of n-ary procedures (e.g. LIST=, CONS*, APPEND, ;;; LSET-UNION) into multiple applications of a primitive two-argument ;;; variant. ;;; - transform applications of the mapping functions (MAP, FOR-EACH, FOLD, ;;; ANY, EVERY) into open-coded loops. The killer here is that these ;;; functions are n-ary. Handling the general case is quite inefficient, ;;; requiring many intermediate data structures to be allocated and ;;; discarded. ;;; - transform applications of procedures that take optional arguments ;;; into calls to variants that do not take optional arguments. This ;;; eliminates unnecessary consing and parsing of the rest parameter. ;;; ;;; These transforms would provide BIG speedups. In particular, the n-ary ;;; mapping functions are particularly slow and cons-intensive, and are good ;;; candidates for tuning. I have coded fast paths for the single-list cases, ;;; but what you really want to do is exploit the fact that the compiler ;;; usually knows how many arguments are being passed to a particular ;;; application of these functions -- they are usually explicitly called, not ;;; passed around as higher-order values. If you can arrange to have your ;;; compiler produce custom code or custom linkages based on the number of ;;; arguments in the call, you can speed these functions up a *lot*. But this ;;; kind of compiler technology no longer exists in the Scheme world as far as ;;; I can see. ;;; ;;; Note that this code is, of course, dependent upon standard bindings for ;;; the R5RS procedures -- i.e., it assumes that the variable CAR is bound ;;; to the procedure that takes the car of a list. If your Scheme ;;; implementation allows user code to alter the bindings of these procedures ;;; in a manner that would be visible to these definitions, then there might ;;; be trouble. You could consider horrible kludgery along the lines of ;;; (define fact ;;; (let ((= =) (- -) (* *)) ;;; (letrec ((real-fact (lambda (n) ;;; (if (= n 0) 1 (* n (real-fact (- n 1))))))) ;;; real-fact))) ;;; Or you could consider shifting to a reasonable Scheme system that, say, ;;; has a module system protecting code from this kind of lossage. ;;; ;;; This code does a fair amount of run-time argument checking. If your ;;; Scheme system has a sophisticated compiler that can eliminate redundant ;;; error checks, this is no problem. However, if not, these checks incur ;;; some performance overhead -- and, in a safe Scheme implementation, they ;;; are in some sense redundant: if we don't check to see that the PROC ;;; parameter is a procedure, we'll find out anyway three lines later when ;;; we try to call the value. It's pretty easy to rip all this argument ;;; checking code out if it's inappropriate for your implementation -- just ;;; nuke every call to CHECK-ARG. ;;; ;;; On the other hand, if you *do* have a sophisticated compiler that will ;;; actually perform soft-typing and eliminate redundant checks (Rice's systems ;;; being the only possible candidate of which I'm aware), leaving these checks ;;; in can *help*, since their presence can be elided in redundant cases, ;;; and in cases where they are needed, performing the checks early, at ;;; procedure entry, can "lift" a check out of a loop. ;;; ;;; Finally, I have only checked the properties that can portably be checked ;;; with R5RS Scheme -- and this is not complete. You may wish to alter ;;; the CHECK-ARG parameter checks to perform extra, implementation-specific ;;; checks, such as procedure arity for higher-order values. ;;; ;;; The code has only these non-R4RS dependencies: ;;; A few calls to an ERROR procedure; ;;; Uses of the R5RS multiple-value procedure VALUES and the m-v binding ;;; RECEIVE macro (which isn't R5RS, but is a trivial macro). ;;; Many calls to a parameter-checking procedure check-arg: ;;; (define (check-arg pred val caller) ;;; (let lp ((val val)) ;;; (if (pred val) val (lp (error "Bad argument" val pred caller))))) ;;; A few uses of the LET-OPTIONAL and :OPTIONAL macros for parsing ;;; optional arguments. ;;; ;;; Most of these procedures use the NULL-LIST? test to trigger the ;;; base case in the inner loop or recursion. The NULL-LIST? function ;;; is defined to be a careful one -- it raises an error if passed a ;;; non-nil, non-pair value. The spec allows an implementation to use ;;; a less-careful implementation that simply defines NULL-LIST? to ;;; be NOT-PAIR?. This would speed up the inner loops of these procedures ;;; at the expense of having them silently accept dotted lists. ;;; A note on dotted lists: ;;; I, personally, take the view that the only consistent view of lists ;;; in Scheme is the view that *everything* is a list -- values such as ;;; 3 or "foo" or 'bar are simply empty dotted lists. This is due to the ;;; fact that Scheme actually has no true list type. It has a pair type, ;;; and there is an *interpretation* of the trees built using this type ;;; as lists. ;;; ;;; I lobbied to have these list-processing procedures hew to this ;;; view, and accept any value as a list argument. I was overwhelmingly ;;; overruled during the SRFI discussion phase. So I am inserting this ;;; text in the reference lib and the SRFI spec as a sort of "minority ;;; opinion" dissent. ;;; ;;; Many of the procedures in this library can be trivially redefined ;;; to handle dotted lists, just by changing the NULL-LIST? base-case ;;; check to NOT-PAIR?, meaning that any non-pair value is taken to be ;;; an empty list. For most of these procedures, that's all that is ;;; required. ;;; ;;; However, we have to do a little more work for some procedures that ;;; *produce* lists from other lists. Were we to extend these procedures to ;;; accept dotted lists, we would have to define how they terminate the lists ;;; produced as results when passed a dotted list. I designed a coherent set ;;; of termination rules for these cases; this was posted to the SRFI-1 ;;; discussion list. I additionally wrote an earlier version of this library ;;; that implemented that spec. It has been discarded during later phases of ;;; the definition and implementation of this library. ;;; ;;; The argument *against* defining these procedures to work on dotted ;;; lists is that dotted lists are the rare, odd case, and that by ;;; arranging for the procedures to handle them, we lose error checking ;;; in the cases where a dotted list is passed by accident -- e.g., when ;;; the programmer swaps a two arguments to a list-processing function, ;;; one being a scalar and one being a list. For example, ;;; (member '(1 3 5 7 9) 7) ;;; This would quietly return #f if we extended MEMBER to accept dotted ;;; lists. ;;; ;;; The SRFI discussion record contains more discussion on this topic. ;; JBC, 2003-10-20: some of the names provided by list.rkt are prefixed ;; with an s: to avoid colliding with mzscheme. The wrapper 1.rkt ;; changes their names back to the non-prefixed form. #lang scheme/base (require "cons.rkt" "selector.rkt" "predicate.rkt" "misc.rkt" (rename-in "fold.rkt" [map s:map] [for-each s:for-each]) (rename-in "search.rkt" [member s:member]) (rename-in "filter.rkt" [remove s:remove]) "delete.rkt" (rename-in "alist.rkt" [assoc s:assoc]) "lset.rkt") (provide (all-from-out "cons.rkt") (all-from-out "selector.rkt") (all-from-out "predicate.rkt") (all-from-out "misc.rkt") (all-from-out "fold.rkt") (all-from-out "search.rkt") (all-from-out "filter.rkt") (all-from-out "delete.rkt") (all-from-out "alist.rkt") (all-from-out "lset.rkt"))