This is the mail archive of the guile@cygnus.com mailing list for the guile project.
Index Nav: | [Date Index] [Subject Index] [Author Index] [Thread Index] | |
---|---|---|
Message Nav: | [Date Prev] [Date Next] | [Thread Prev] [Thread Next] |
jglascoe@jay.giss.nasa.gov writes: > > > > Hmm. I think your definition of slow and mine are different. Could > > you clarify what you mean when you say 'slow'? How do you account for > > the performance of Stalin? > > > > a while back I posted something like this: > > hash->keys could be written in Scheme like this > > (define entry->key cadr) > > (define hash->keys > (lambda (mytab) > ;; maybe do some type-checking here > (let* ((vec (cdr mytab)) > (vec-len (vector-length vec))) > (do ((keys '()) > (i 0 (+ i 1)) > (bucket (vector-ref vec 0) (vector-ref vec i))) > ((>= i vec-len) keys) > (do ((entry-list bucket (cdr entry-list))) > ((null? entry-list)) > (let* ((entry (car entry-list)) > (key (entry->key entry))) > (set! keys (cons key keys)))))))) > > here's what I would call a functional version: > > (define hash->keys > (lambda (mytab) > (let ((bucket->keys (lambda (bucket) (map entry->key bucket))) > (entry->key cadr)) > (apply append (map bucket->keys (vector->list (cdr mytab))))))) > > the functional guy is sleek and pretty, but those "vector->list" and > "append" bits are performance killers. > How about: (define (hash->keys mytab) (let ((real-tab (cdr mytab)) (entry->key cadr) (end (vector-length real-tab))) (let loop-over-tab ((index 0) (accum ())) (if (= index end) accum (loop-over-tab (+ index 1) (let loop-over-bucket ((l (vector-ref real-tab index)) (accum accum)) (if (null? l) accum (loop-over-bucket (cdr l) (cons (car l) accum))))))))) This is a common style of programming once you have gotten past the prototyping phase and want to write purely functional code that is notheless tail-recursive, avoids gratuitous allocation, and does not need to append (which requires copies). Such code is efficient and optimizes very well. Actually, since all the named-let recursive calls are tail calls, the named lets can be rewritten as do loops : (define (hash->keys mytab) (let ((real-tab (cdr mytab)) (entry->key cadr) (end (vector-length real-tab))) (do ((index 0 (+ 1 index)) (accum () (do ((l (vector-ref real-tab index) (cdr l)) (accum accum (cons (car l) accum))) ((null? l)) accum))) ((= index end)) accum))) This is much like your original _non_-functional version, but actually a bit shorter. Since it avoids side effects, it will also likely compile to more efficient code (if it is getting compiled) or just plain run faster since it doesn't have to set! variables if interpreted. An sufficiently smart compiler that had inside knowledge about `reverse' `map' and `vector->list' might actually transform your functional version into something close to the code above, but with a big reverse around all of it (requiring one gratuitous copy), since your purely functional version returns keys in the reverse of the order of the other one. There do exist compilers that are smart enough to do such transformations. In particular, I think Stalin knows how to translate maps into iterations (someone correct me if I'm wrong). If your point was just that the obvious and easy way to write Scheme code that is inefficient unless you have a really smart compiler, I'll grant your premise, but rewriting working simple-minded code to functional but efficient code is not hard even by hand, and smart compilers do exist. - Maciej