mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-04-30 03:40:34 +02:00
Add SRFI-171 to guile
This adds SRFI-171 (transducers) to guile. The two guile-specific additions are powerful transducers which can be used to generalize transducers like tsegment. They are hard to get right, but powerful and useful enough to warrant inclusion. * doc/ref/srfi-modules.texi: added srfi-171 section * module/Makefile.am (SOURCES): * module/srfi/srfi-171.scm: * module/srfi/srfi-171/meta.scm: Add SRFI-171 * module/srfi/srfi-171/gnu.scm: Add 2 guile-specific extensions. * test-suite/Makefile.am (SCM_TESTS): * test-suite/tests/srfi-171.test: Add tests. Signed-off-by: Ludovic Courtès <ludo@gnu.org>
This commit is contained in:
parent
f8f8986e4a
commit
5f60eb6bb5
7 changed files with 1393 additions and 0 deletions
|
@ -64,6 +64,7 @@ get the relevant SRFI documents from the SRFI home page
|
||||||
* SRFI-98:: Accessing environment variables.
|
* SRFI-98:: Accessing environment variables.
|
||||||
* SRFI-105:: Curly-infix expressions.
|
* SRFI-105:: Curly-infix expressions.
|
||||||
* SRFI-111:: Boxes.
|
* SRFI-111:: Boxes.
|
||||||
|
* SRFI-171:: Transducers
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
|
|
||||||
|
@ -5602,6 +5603,492 @@ Return the current contents of @var{box}.
|
||||||
Set the contents of @var{box} to @var{value}.
|
Set the contents of @var{box} to @var{value}.
|
||||||
@end deffn
|
@end deffn
|
||||||
|
|
||||||
|
@node SRFI-171
|
||||||
|
@subsection Transducers
|
||||||
|
@cindex SRFI-171
|
||||||
|
@cindex transducers
|
||||||
|
|
||||||
|
Some of the most common operations used in the Scheme language are those
|
||||||
|
transforming lists: map, filter, take and so on. They work well, are well
|
||||||
|
understood, and are used daily by most Scheme programmers. They are however not
|
||||||
|
general because they only work on lists, and they do not compose very well
|
||||||
|
since combining N of them builds @code{(- N 1)} intermediate lists.
|
||||||
|
|
||||||
|
Transducers are oblivious to what kind of process they are used in, and
|
||||||
|
are composable without building intermediate collections. This means we
|
||||||
|
can create a transducer that squares all even numbers:
|
||||||
|
|
||||||
|
@example
|
||||||
|
(compose (tfilter odd?) (tmap (lambda (x) (* x x))))
|
||||||
|
@end example
|
||||||
|
|
||||||
|
and reuse it with lists, vectors, or in just about any context where
|
||||||
|
data flows in one direction. We could use it as a processing step for
|
||||||
|
asynchronous channels, with an event framework as a pre-processing step,
|
||||||
|
or even in lazy contexts where you pass a lazy collection and a
|
||||||
|
transducer to a function and get a new lazy collection back.
|
||||||
|
|
||||||
|
The traditional Scheme approach of having collection-specific procedures
|
||||||
|
is not changed. We instead specify a general form of transformations
|
||||||
|
that complement these procedures. The benefits are obvious: a clear,
|
||||||
|
well-understood way of describing common transformations in a way that
|
||||||
|
is faster than just chaining the collection-specific counterparts. For
|
||||||
|
guile in particular this means a lot better GC performance.
|
||||||
|
|
||||||
|
Notice however that @code{(compose @dots{})} composes transducers
|
||||||
|
left-to-right, due to how transducers are initiated.
|
||||||
|
|
||||||
|
@menu
|
||||||
|
* SRFI-171 General Discussion:: General information about transducers
|
||||||
|
* SRFI-171 Applying Transducers:: Documentation of collection-specific forms
|
||||||
|
* SRFI-171 Reducers:: Reducers specified by the SRFI
|
||||||
|
* SRFI-171 Transducers:: Transducers specified by the SRFI
|
||||||
|
* SRFI-171 Helpers:: Utilities for writing your own transducers
|
||||||
|
@end menu
|
||||||
|
|
||||||
|
@node SRFI-171 General Discussion
|
||||||
|
@subsubsection SRFI-171 General Discussion
|
||||||
|
@cindex transducers discussion
|
||||||
|
|
||||||
|
@subheading The concept of reducers
|
||||||
|
The central part of transducers are 3-arity reducing procedures.
|
||||||
|
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
no arguments: Produces the identity of the reducer.
|
||||||
|
|
||||||
|
@item
|
||||||
|
(result-so-far): completion. Returns @code{result-so-far} either with or
|
||||||
|
without transforming it first.
|
||||||
|
|
||||||
|
@item
|
||||||
|
(result-so-far input) combines @code{result-so-far} and @code{input} to produce
|
||||||
|
a new @code{result-so-far}.
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
In the case of a summing @code{+} reducer, the reducer would produce, in
|
||||||
|
arity order: @code{0}, @code{result-so-far}, @code{(+ result-so-far
|
||||||
|
input)}. This happens to be exactly what the regular @code{+} does.
|
||||||
|
|
||||||
|
@subheading The concept of transducers
|
||||||
|
A transducer is a one-arity procedure that takes a reducer and produces a
|
||||||
|
reducing function that behaves as follows:
|
||||||
|
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
no arguments: calls reducer with no arguments (producing its identity)
|
||||||
|
|
||||||
|
@item
|
||||||
|
(result-so-far): Maybe transform the result-so-far and call reducer with it.
|
||||||
|
|
||||||
|
@item
|
||||||
|
(result-so-far input) Maybe do something to input and maybe call the
|
||||||
|
reducer with result-so-far and the maybe-transformed input.
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
A simple example is as following:
|
||||||
|
|
||||||
|
@example
|
||||||
|
(list-transduce (tfilter odd?)+ '(1 2 3 4 5)).
|
||||||
|
@end example
|
||||||
|
|
||||||
|
This first returns a transducer filtering all odd
|
||||||
|
elements, then it runs @code{+} without arguments to retrieve its
|
||||||
|
identity. It then starts the transduction by passing @code{+} to the
|
||||||
|
transducer returned by @code{(tfilter odd?)} which returns a reducing
|
||||||
|
function. It works not unlike reduce from SRFI 1, but also checks
|
||||||
|
whether one of the intermediate transducers returns a "reduced" value
|
||||||
|
(implemented as a SRFI 9 record), which means the reduction finished
|
||||||
|
early.
|
||||||
|
|
||||||
|
Because transducers compose and the final reduction is only executed in
|
||||||
|
the last step, composed transducers will not build any intermediate
|
||||||
|
result or collections. Although the normal way of thinking about
|
||||||
|
application of composed functions is right to left, due to how the
|
||||||
|
transduction is built it is applied left to right. @code{(compose
|
||||||
|
(tfilter odd?) (tmap sqrt))} will create a transducer that first filters
|
||||||
|
out any odd values and then computes the square root of the rest.
|
||||||
|
|
||||||
|
|
||||||
|
@subheading State
|
||||||
|
Even though transducers appear to be somewhat of a generalisation of
|
||||||
|
@code{map} and friends, this is not really true. Since transducers don't
|
||||||
|
know in which context they are being used, some transducers must keep
|
||||||
|
state where their collection-specific counterparts do not. The
|
||||||
|
transducers that keep state do so using hidden mutable state, and as
|
||||||
|
such all the caveats of mutation, parallelism, and multi-shot
|
||||||
|
continuations apply. Each transducer keeping state is clearly described
|
||||||
|
as doing so in the documentation.
|
||||||
|
|
||||||
|
@subheading Naming
|
||||||
|
|
||||||
|
Reducers exported from the transducers module are named as in their
|
||||||
|
SRFI-1 counterpart, but prepended with an r. Transducers also follow
|
||||||
|
that naming, but are prepended with a t.
|
||||||
|
|
||||||
|
|
||||||
|
@node SRFI-171 Applying Transducers
|
||||||
|
@subsubsection Applying Transducers
|
||||||
|
@cindex transducers applying
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} list-transduce xform f lst
|
||||||
|
@deffnx {Scheme Procedure} list-transduce xform f identity lst
|
||||||
|
Initialize the transducer @var{xform} by passing the reducer @var{f}
|
||||||
|
to it. If no identity is provided, @var{f} runs without arguments to
|
||||||
|
return the reducer identity. It then reduces over @var{lst} using the
|
||||||
|
identity as the seed.
|
||||||
|
|
||||||
|
If one of the transducers finishes early (such as @code{ttake} or
|
||||||
|
@code{tdrop}), it communicates this by returning a reduced value, which
|
||||||
|
in the guile implementation is just a value wrapped in a SRFI 9 record
|
||||||
|
type named ``reduced''. If such a value is returned by the transducer,
|
||||||
|
@code{list-transduce} must stop execution and return an unreduced value
|
||||||
|
immediately.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} vector-transduce xform f vec
|
||||||
|
@deffnx {Scheme Procedure} vector-transduce xform f identity vec
|
||||||
|
@deffnx {Scheme Procedure} string-transduce xform f str
|
||||||
|
@deffnx {Scheme Procedure} string-transduce xform f identity str
|
||||||
|
@deffnx {Scheme Procedure} bytevector-u8-transduce xform f bv
|
||||||
|
@deffnx {Scheme Procedure} bytevector-u8-transduce xform f identity bv
|
||||||
|
@deffnx {Scheme Procedure} generator-transduce xform f gen
|
||||||
|
@deffnx {Scheme Procedure} generator-transduce xform f identity gen
|
||||||
|
|
||||||
|
Same as @code{list-transduce}, but for vectors, strings, u8-bytevectors
|
||||||
|
and SRFI-158-styled generators respectively.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} port-transduce xform f reader
|
||||||
|
@deffnx {Scheme Procedure} port-transduce xform f reader port
|
||||||
|
@deffnx {Scheme Procedure} port-transduce xform f identity reader port
|
||||||
|
|
||||||
|
Same as @code{list-reduce} but for ports. Called without a port, it
|
||||||
|
reduces over the results of applying @var{(reader)} until the
|
||||||
|
EOF-object is returned, presumably to read from
|
||||||
|
@code{current-input-port}. With a port @var{reader} is applied to
|
||||||
|
@var{port} instead of without any arguments. If @var{identity} is
|
||||||
|
provided, that is used as the initial identity in the reduction.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
|
||||||
|
@node SRFI-171 Reducers
|
||||||
|
@subsubsection Reducers
|
||||||
|
@cindex transducers reducers
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} rcons
|
||||||
|
a simple consing reducer. When called without values, it returns its
|
||||||
|
identity, @code{'()}. With one value, which will be a list, it reverses
|
||||||
|
the list (using @code{reverse!}). When called with two values, it conses
|
||||||
|
the second value to the first.
|
||||||
|
|
||||||
|
@example
|
||||||
|
(list-transduce (tmap (lambda (x) (+ x 1)) rcons (list 0 1 2 3))
|
||||||
|
@result{} (1 2 3 4)
|
||||||
|
@end example
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} reverse-rcons
|
||||||
|
same as rcons, but leaves the values in their reversed order.
|
||||||
|
@example
|
||||||
|
(list-transduce (tmap (lambda (x) (+ x 1))) reverse-rcons (list 0 1 2 3))
|
||||||
|
@result{} (4 3 2 1)
|
||||||
|
@end example
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} rany pred?
|
||||||
|
The reducer version of any. Returns @code{(reduced (pred? value))} if
|
||||||
|
any @code{(pred? value)} returns non-#f. The identity is #f.
|
||||||
|
|
||||||
|
@example
|
||||||
|
(list-transduce (tmap (lambda (x) (+ x 1))) (rany odd?) (list 1 3 5))
|
||||||
|
@result{} #f
|
||||||
|
|
||||||
|
(list-transduce (tmap (lambda (x) (+ x 1))) (rany odd?) (list 1 3 4 5))
|
||||||
|
@result{} #t
|
||||||
|
@end example
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} revery pred?
|
||||||
|
The reducer version of every. Stops the transduction and returns
|
||||||
|
@code{(reduced #f)} if any @code{(pred? value)} returns #f. If every
|
||||||
|
@code{(pred? value)} returns true, it returns the result of the last
|
||||||
|
invocation of @code{(pred? value)}. The identity is #t.
|
||||||
|
|
||||||
|
@example
|
||||||
|
(list-transduce
|
||||||
|
(tmap (lambda (x) (+ x 1)))
|
||||||
|
(revery (lambda (v) (if (odd? v) v #f)))
|
||||||
|
(list 2 4 6))
|
||||||
|
@result{} 7
|
||||||
|
|
||||||
|
(list-transduce (tmap (lambda (x) (+ x 1)) (revery odd?) (list 2 4 5 6))
|
||||||
|
@result{} #f
|
||||||
|
@end example
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} rcount
|
||||||
|
A simple counting reducer. Counts the values that pass through the
|
||||||
|
transduction.
|
||||||
|
@example
|
||||||
|
(list-transduce (tfilter odd?) rcount (list 1 2 3 4)) @result{} 2.
|
||||||
|
@end example
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
|
||||||
|
@node SRFI-171 Transducers
|
||||||
|
@subsubsection Transducers
|
||||||
|
@cindex transducers transducers
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tmap proc
|
||||||
|
Returns a transducer that applies @var{proc} to all values. Stateless.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn tfilter pred?
|
||||||
|
Returns a transducer that removes values for which @var{pred?} returns #f.
|
||||||
|
|
||||||
|
Stateless.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tremove pred?
|
||||||
|
Returns a transducer that removes values for which @var{pred?} returns non-#f.
|
||||||
|
|
||||||
|
Stateless
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tfilter-map proc
|
||||||
|
The same as @code{(compose (tmap proc) (tfilter values))}. Stateless.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} treplace mapping
|
||||||
|
The argument @var{mapping} is an association list (using @code{equal?}
|
||||||
|
to compare keys), a hash-table, a one-argument procedure taking one
|
||||||
|
argument and either producing that same argument or a replacement value.
|
||||||
|
|
||||||
|
Returns a transducer which checks for the presence of any value passed
|
||||||
|
through it in mapping. If a mapping is found, the value of that mapping
|
||||||
|
is returned, otherwise it just returns the original value.
|
||||||
|
|
||||||
|
Does not keep internal state, but modifying the mapping while it's in
|
||||||
|
use by treplace is an error.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tdrop n
|
||||||
|
Returns a transducer that discards the first @var{n} values.
|
||||||
|
|
||||||
|
Stateful.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} ttake n
|
||||||
|
Returns a transducer that discards all values and stops the transduction
|
||||||
|
after the first @var{n} values have been let through. Any subsequent values
|
||||||
|
are ignored.
|
||||||
|
|
||||||
|
Stateful.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tdrop-while pred?
|
||||||
|
Returns a transducer that discards the the first values for which
|
||||||
|
@var{pred?} returns true.
|
||||||
|
|
||||||
|
Stateful.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} ttake-while pred?
|
||||||
|
@deffnx {Scheme Procedure} ttake-while pred? retf
|
||||||
|
Returns a transducer that stops the transduction after @var{pred?} has
|
||||||
|
returned #f. Any subsequent values are ignored and the last successful
|
||||||
|
value is returned. @var{retf} is a function that gets called whenever
|
||||||
|
@var{pred?} returns false. The arguments passed are the result so far
|
||||||
|
and the input for which pred? returns @code{#f}. The default function is
|
||||||
|
@code{(lambda (result input) result)}.
|
||||||
|
|
||||||
|
Stateful.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tconcatenate
|
||||||
|
tconcatenate @emph{is} a transducer that concatenates the content of
|
||||||
|
each value (that must be a list) into the reduction.
|
||||||
|
@example
|
||||||
|
(list-transduce tconcatenate rcons '((1 2) (3 4 5) (6 (7 8) 9)))
|
||||||
|
@result{} (1 2 3 4 5 6 (7 8) 9)
|
||||||
|
@end example
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tappend-map proc
|
||||||
|
The same as @code{(compose (tmap proc) tconcatenate)}.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tflatten
|
||||||
|
tflatten @emph{is} a transducer that flattens an input consisting of lists.
|
||||||
|
|
||||||
|
@example
|
||||||
|
(list-transduce tflatten rcons '((1 2) 3 (4 (5 6) 7 8) 9)
|
||||||
|
@result{} (1 2 3 4 5 6 7 8 9)
|
||||||
|
@end example
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tdelete-neighbor-duplicates
|
||||||
|
@deffnx {Scheme Procedure} tdelete-neighbor-duplicates equality-predicate
|
||||||
|
Returns a transducer that removes any directly following duplicate
|
||||||
|
elements. The default @var{equality-predicate} is @code{equal?}.
|
||||||
|
|
||||||
|
Stateful.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tdelete-duplicates
|
||||||
|
@deffnx {Scheme Procedure} tdelete-duplicates equality-predicate
|
||||||
|
Returns a transducer that removes any subsequent duplicate elements
|
||||||
|
compared using @var{equality-predicate}. The default
|
||||||
|
@var{equality-predicate} is @code{equal?}.
|
||||||
|
|
||||||
|
Stateful.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tsegment n
|
||||||
|
Returns a transducer that groups @var{n} inputs in lists of @var{n}
|
||||||
|
elements. When the transduction stops, it flushes any remaining
|
||||||
|
collection, even if it contains fewer than @var{n} elements.
|
||||||
|
|
||||||
|
Stateful.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tpartition pred?
|
||||||
|
Returns a transducer that groups inputs in lists by whenever
|
||||||
|
@code{(pred? input)} changes value.
|
||||||
|
|
||||||
|
Stateful.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tadd-between value
|
||||||
|
Returns a transducer which interposes @var{value} between each value
|
||||||
|
and the next. This does not compose gracefully with transducers like
|
||||||
|
@code{ttake}, as you might end up ending the transduction on
|
||||||
|
@code{value}.
|
||||||
|
|
||||||
|
Stateful.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tenumerate
|
||||||
|
@deffnx {Scheme Procedure} tenumerate start
|
||||||
|
Returns a transducer that indexes values passed through it, starting at
|
||||||
|
@var{start}, which defaults to 0. The indexing is done through cons
|
||||||
|
pairs like @code{(index . input)}.
|
||||||
|
|
||||||
|
@example
|
||||||
|
(list-transduce (tenumerate 1) rcons (list 'first 'second 'third))
|
||||||
|
@result{} ((1 . first) (2 . second) (3 . third))
|
||||||
|
@end example
|
||||||
|
|
||||||
|
Stateful.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tlog
|
||||||
|
@deffnx {Scheme Procedure} tlog logger
|
||||||
|
Returns a transducer that can be used to log or print values and
|
||||||
|
results. The result of the @var{logger} procedure is discarded. The
|
||||||
|
default @var{logger} is @code{(lambda (result input) (write input)
|
||||||
|
(newline))}.
|
||||||
|
|
||||||
|
Stateless.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@subheading Guile-specific transducers
|
||||||
|
These transducers are available in the @code{(srfi srfi-171 gnu)}
|
||||||
|
library, and are provided outside the standard described by the SRFI-171
|
||||||
|
document.
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tbatch reducer
|
||||||
|
@deffnx {Scheme Procedure} tbatch transducer reducer
|
||||||
|
A batching transducer that accumulates results using @var{reducer} or
|
||||||
|
@code{((transducer) reducer)} until it returns a reduced value. This can
|
||||||
|
be used to generalize something like @code{tsegment}:
|
||||||
|
|
||||||
|
@example
|
||||||
|
;; This behaves exactly like (tsegment 4).
|
||||||
|
(list-transduce (tbatch (ttake 4) rcons) rcons (iota 10))
|
||||||
|
@result {} ((0 1 2 3) (4 5 6 7) (8 9))
|
||||||
|
@end example
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} tfold reducer
|
||||||
|
@deffnx {Scheme Procedure} tfold reducer seed
|
||||||
|
|
||||||
|
A folding transducer that yields the result of @code{(reducer seed
|
||||||
|
value)}, saving it's result between iterations.
|
||||||
|
|
||||||
|
@example
|
||||||
|
(list-transduce (tfold +) rcons (iota 10))
|
||||||
|
@result{} (0 1 3 6 10 15 21 28 36 45)
|
||||||
|
@end example
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
|
||||||
|
@node SRFI-171 Helpers
|
||||||
|
@subsubsection Helper functions for writing transducers
|
||||||
|
@cindex transducers helpers
|
||||||
|
|
||||||
|
These functions are in the @code{(srfi srfi-171 meta)} module and are only
|
||||||
|
usable when you want to write your own transducers.
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} reduced value
|
||||||
|
Wraps a value in a @code{<reduced>} container, signalling that the
|
||||||
|
reduction should stop.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} reduced? value
|
||||||
|
Returns #t if value is a @code{<reduced>} record.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} unreduce reduced-container
|
||||||
|
Returns the value in reduced-container.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} ensure-reduced value
|
||||||
|
Wraps value in a @code{<reduced>} container if it is not already reduced.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} preserving-reduced reducer
|
||||||
|
Wraps @code{reducer} in another reducer that encapsulates any returned
|
||||||
|
reduced value in another reduced container. This is useful in places
|
||||||
|
where you re-use a reducer with [collection]-reduce. If the reducer
|
||||||
|
returns a reduced value, [collection]-reduce unwraps it. Unless handled,
|
||||||
|
this leads to the reduction continuing.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} list-reduce f identity lst
|
||||||
|
The reducing function used internally by @code{list-transduce}. @var{f}
|
||||||
|
is a reducer as returned by a transducer. @var{identity} is the
|
||||||
|
identity (sometimes called "seed") of the reduction. @var{lst} is a
|
||||||
|
list. If @var{f} returns a reduced value, the reduction stops
|
||||||
|
immediately and the unreduced value is returned.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} vector-reduce f identity vec
|
||||||
|
The vector version of list-reduce.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} string-reduce f identity str
|
||||||
|
The string version of list-reduce.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} bytevector-u8-reduce f identity bv
|
||||||
|
The bytevector-u8 version of list-reduce.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} port-reduce f identity reader port
|
||||||
|
The port version of list-reducer. It reduces over port using reader
|
||||||
|
until reader returns the EOF object.
|
||||||
|
@end deffn
|
||||||
|
|
||||||
|
@deffn {Scheme Procedure} generator-reduce f identity gen
|
||||||
|
The port version of list-reduce. It reduces over @code{gen} until it
|
||||||
|
returns the EOF object
|
||||||
|
@end deffn
|
||||||
|
|
||||||
@c srfi-modules.texi ends here
|
@c srfi-modules.texi ends here
|
||||||
|
|
||||||
@c Local Variables:
|
@c Local Variables:
|
||||||
|
|
|
@ -312,6 +312,9 @@ SOURCES = \
|
||||||
srfi/srfi-88.scm \
|
srfi/srfi-88.scm \
|
||||||
srfi/srfi-98.scm \
|
srfi/srfi-98.scm \
|
||||||
srfi/srfi-111.scm \
|
srfi/srfi-111.scm \
|
||||||
|
srfi/srfi-171.scm \
|
||||||
|
srfi/srfi-171/gnu.scm \
|
||||||
|
srfi/srfi-171/meta.scm \
|
||||||
\
|
\
|
||||||
statprof.scm \
|
statprof.scm \
|
||||||
\
|
\
|
||||||
|
|
457
module/srfi/srfi-171.scm
Normal file
457
module/srfi/srfi-171.scm
Normal file
|
@ -0,0 +1,457 @@
|
||||||
|
;; Copyright (C) 2020 Free Software Foundation, Inc.
|
||||||
|
;;
|
||||||
|
;; This library is free software; you can redistribute it and/or
|
||||||
|
;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;; License as published by the Free Software Foundation; either
|
||||||
|
;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;
|
||||||
|
;; This library is distributed in the hope that it will be useful,
|
||||||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;; Lesser General Public License for more details.
|
||||||
|
;;
|
||||||
|
;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;; License along with this library; if not, write to the Free Software
|
||||||
|
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
(define-module (srfi srfi-171)
|
||||||
|
#:use-module (ice-9 match)
|
||||||
|
#:use-module (srfi srfi-9)
|
||||||
|
#:use-module ((srfi srfi-43) #:select (vector->list))
|
||||||
|
#:use-module ((srfi srfi-69) #:prefix srfi69:)
|
||||||
|
#:use-module ((rnrs hashtables) #:prefix rnrs:)
|
||||||
|
#:use-module (srfi srfi-171 meta)
|
||||||
|
#:export (rcons
|
||||||
|
reverse-rcons
|
||||||
|
rcount
|
||||||
|
rany
|
||||||
|
revery
|
||||||
|
list-transduce
|
||||||
|
vector-transduce
|
||||||
|
string-transduce
|
||||||
|
bytevector-u8-transduce
|
||||||
|
port-transduce
|
||||||
|
generator-transduce
|
||||||
|
|
||||||
|
tmap
|
||||||
|
tfilter
|
||||||
|
tremove
|
||||||
|
treplace
|
||||||
|
tfilter-map
|
||||||
|
tdrop
|
||||||
|
tdrop-while
|
||||||
|
ttake
|
||||||
|
ttake-while
|
||||||
|
tconcatenate
|
||||||
|
tappend-map
|
||||||
|
tdelete-neighbor-duplicates
|
||||||
|
tdelete-duplicates
|
||||||
|
tflatten
|
||||||
|
tsegment
|
||||||
|
tpartition
|
||||||
|
tadd-between
|
||||||
|
tenumerate
|
||||||
|
tlog))
|
||||||
|
(cond-expand-provide (current-module) '(srfi-171))
|
||||||
|
|
||||||
|
|
||||||
|
;; A placeholder for a unique "nothing".
|
||||||
|
(define nothing (list 'nothing))
|
||||||
|
(define (nothing? val)
|
||||||
|
(eq? val nothing))
|
||||||
|
|
||||||
|
;;; Reducing functions meant to be used at the end at the transducing process.
|
||||||
|
(define rcons
|
||||||
|
(case-lambda
|
||||||
|
"A transducer-friendly consing reducer with '() as identity."
|
||||||
|
(() '())
|
||||||
|
((lst) (reverse! lst))
|
||||||
|
((lst x) (cons x lst))))
|
||||||
|
|
||||||
|
(define reverse-rcons
|
||||||
|
(case-lambda
|
||||||
|
"A transducer-friendly consing reducer with '() as identity.
|
||||||
|
The resulting list is in reverse order."
|
||||||
|
(() '())
|
||||||
|
((lst) lst)
|
||||||
|
((lst x) (cons x lst))))
|
||||||
|
|
||||||
|
(define rcount
|
||||||
|
(case-lambda
|
||||||
|
"A counting reducer that counts any elements that made it through the
|
||||||
|
transduction.
|
||||||
|
@example
|
||||||
|
(transduce (tfilter odd?) tcount (list 1 2 3)) @result{} 2
|
||||||
|
@end example"
|
||||||
|
(() 0)
|
||||||
|
((result) result)
|
||||||
|
((result input)
|
||||||
|
(+ 1 result))))
|
||||||
|
|
||||||
|
(define (rany pred)
|
||||||
|
(case-lambda
|
||||||
|
"Return a reducer that tests input using @var{pred}. If any input satisfies
|
||||||
|
@var{pred}, return @code{(reduced value)}."
|
||||||
|
(() #f)
|
||||||
|
((result) result)
|
||||||
|
((result input)
|
||||||
|
(let ((test (pred input)))
|
||||||
|
(if test
|
||||||
|
(reduced test)
|
||||||
|
#f)))))
|
||||||
|
|
||||||
|
(define (revery pred)
|
||||||
|
(case-lambda
|
||||||
|
"Returns a reducer that tests input using @var{pred}. If any input satisfies
|
||||||
|
@var{pred}, it returns @code{(reduced #f)}."
|
||||||
|
(() #t)
|
||||||
|
((result) result)
|
||||||
|
((result input)
|
||||||
|
(let ((test (pred input)))
|
||||||
|
(if (and result test)
|
||||||
|
test
|
||||||
|
(reduced #f))))))
|
||||||
|
|
||||||
|
|
||||||
|
(define list-transduce
|
||||||
|
(case-lambda
|
||||||
|
((xform f coll)
|
||||||
|
(list-transduce xform f (f) coll))
|
||||||
|
((xform f init coll)
|
||||||
|
(let* ((xf (xform f))
|
||||||
|
(result (list-reduce xf init coll)))
|
||||||
|
(xf result)))))
|
||||||
|
|
||||||
|
(define vector-transduce
|
||||||
|
(case-lambda
|
||||||
|
((xform f coll)
|
||||||
|
(vector-transduce xform f (f) coll))
|
||||||
|
((xform f init coll)
|
||||||
|
(let* ((xf (xform f))
|
||||||
|
(result (vector-reduce xf init coll)))
|
||||||
|
(xf result)))))
|
||||||
|
|
||||||
|
(define string-transduce
|
||||||
|
(case-lambda
|
||||||
|
((xform f coll)
|
||||||
|
(string-transduce xform f (f) coll))
|
||||||
|
((xform f init coll)
|
||||||
|
(let* ((xf (xform f))
|
||||||
|
(result (string-reduce xf init coll)))
|
||||||
|
(xf result)))))
|
||||||
|
|
||||||
|
(define bytevector-u8-transduce
|
||||||
|
(case-lambda
|
||||||
|
((xform f coll)
|
||||||
|
(bytevector-u8-transduce xform f (f) coll))
|
||||||
|
((xform f init coll)
|
||||||
|
(let* ((xf (xform f))
|
||||||
|
(result (bytevector-u8-reduce xf init coll)))
|
||||||
|
(xf result)))))
|
||||||
|
|
||||||
|
(define port-transduce
|
||||||
|
(case-lambda
|
||||||
|
((xform f by)
|
||||||
|
(generator-transduce xform f by))
|
||||||
|
((xform f by port)
|
||||||
|
(port-transduce xform f (f) by port))
|
||||||
|
((xform f init by port)
|
||||||
|
(let* ((xf (xform f))
|
||||||
|
(result (port-reduce xf init by port)))
|
||||||
|
(xf result)))))
|
||||||
|
|
||||||
|
(define generator-transduce
|
||||||
|
(case-lambda
|
||||||
|
((xform f gen)
|
||||||
|
(generator-transduce xform f (f) gen))
|
||||||
|
((xform f init gen)
|
||||||
|
(let* ((xf (xform f))
|
||||||
|
(result (generator-reduce xf init gen)))
|
||||||
|
(xf result)))))
|
||||||
|
|
||||||
|
;;; Transducers
|
||||||
|
(define (tmap f)
|
||||||
|
(lambda (reducer)
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(reducer result (f input))))))
|
||||||
|
|
||||||
|
(define (tfilter pred)
|
||||||
|
(lambda (reducer)
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(if (pred input)
|
||||||
|
(reducer result input)
|
||||||
|
result)))))
|
||||||
|
|
||||||
|
(define (tremove pred)
|
||||||
|
(lambda (reducer)
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(if (not (pred input))
|
||||||
|
(reducer result input)
|
||||||
|
result)))))
|
||||||
|
|
||||||
|
(define (tfilter-map f)
|
||||||
|
(compose (tmap f) (tfilter values)))
|
||||||
|
|
||||||
|
(define (make-replacer map)
|
||||||
|
(cond
|
||||||
|
((list? map)
|
||||||
|
(lambda (x)
|
||||||
|
(match (assoc x map)
|
||||||
|
((_ . replacer) replacer)
|
||||||
|
(#f x))))
|
||||||
|
((srfi69:hash-table? map)
|
||||||
|
(lambda (x)
|
||||||
|
(srfi69:hash-table-ref/default map x x)))
|
||||||
|
((rnrs:hashtable? map)
|
||||||
|
(lambda (x)
|
||||||
|
(rnrs:hashtable-ref map x x)))
|
||||||
|
((hash-table? map)
|
||||||
|
(lambda (x)
|
||||||
|
(hash-ref map x x)))
|
||||||
|
((procedure? map) map)
|
||||||
|
(else
|
||||||
|
(error "Unsupported mapping in treplace" map))))
|
||||||
|
|
||||||
|
|
||||||
|
(define (treplace map)
|
||||||
|
"Return a transducer that searches for any input in @var{map}, which may
|
||||||
|
be a guile native hashtable, an R6RS hashtable, a srfi-69 hashtable, an alist
|
||||||
|
or a one-argument procedure taking one value and producing either the same
|
||||||
|
value or a replacement one. Alists and guile-native hashtbles compare keys
|
||||||
|
using @code{equal?} whereas the other mappings use whatever equality predicate
|
||||||
|
they were created with."
|
||||||
|
(tmap (make-replacer map)))
|
||||||
|
|
||||||
|
(define (tdrop n)
|
||||||
|
(lambda (reducer)
|
||||||
|
(let ((new-n (+ 1 n)))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(set! new-n (- new-n 1))
|
||||||
|
(if (positive? new-n)
|
||||||
|
result
|
||||||
|
(reducer result input)))))))
|
||||||
|
|
||||||
|
(define (tdrop-while pred)
|
||||||
|
(lambda (reducer)
|
||||||
|
(let ((drop? #t))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(if (and (pred input) drop?)
|
||||||
|
result
|
||||||
|
(begin
|
||||||
|
(set! drop? #f)
|
||||||
|
(reducer result input))))))))
|
||||||
|
|
||||||
|
(define (ttake n)
|
||||||
|
(lambda (reducer)
|
||||||
|
;; we need to reset new-n for every new transduction
|
||||||
|
(let ((new-n n))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(let ((result (if (positive? new-n)
|
||||||
|
(reducer result input)
|
||||||
|
result)))
|
||||||
|
(set! new-n (- new-n 1))
|
||||||
|
(if (not (positive? new-n))
|
||||||
|
(ensure-reduced result)
|
||||||
|
result)))))))
|
||||||
|
|
||||||
|
(define ttake-while
|
||||||
|
(case-lambda
|
||||||
|
((pred) (ttake-while pred (lambda (result input) result)))
|
||||||
|
((pred retf)
|
||||||
|
(lambda (reducer)
|
||||||
|
(let ((take? #t))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(if (and take? (pred input))
|
||||||
|
(reducer result input)
|
||||||
|
(begin
|
||||||
|
(set! take? #f)
|
||||||
|
(ensure-reduced (retf result input)))))))))))
|
||||||
|
|
||||||
|
(define (tconcatenate reducer)
|
||||||
|
(let ((preserving-reducer (preserving-reduced reducer)))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(list-reduce preserving-reducer result input)))))
|
||||||
|
|
||||||
|
(define (tappend-map f)
|
||||||
|
(compose (tmap f) tconcatenate))
|
||||||
|
|
||||||
|
(define (tflatten reducer)
|
||||||
|
"tflatten is a transducer that flattens any list passed through it.
|
||||||
|
@example
|
||||||
|
(list-transduce tflatten conj (list 1 2 (list 3 4 '(5 6) 7 8)))
|
||||||
|
@result{} (1 2 3 4 5 6 7 8)
|
||||||
|
@end example"
|
||||||
|
(case-lambda
|
||||||
|
(() '())
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(if (list? input)
|
||||||
|
(list-reduce (preserving-reduced (tflatten reducer)) result input)
|
||||||
|
(reducer result input)))))
|
||||||
|
|
||||||
|
|
||||||
|
(define tdelete-neighbor-duplicates
|
||||||
|
(case-lambda
|
||||||
|
(() (tdelete-neighbor-duplicates equal?))
|
||||||
|
((equality-pred?)
|
||||||
|
(lambda (reducer)
|
||||||
|
(let ((prev nothing))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(if (equality-pred? prev input)
|
||||||
|
result
|
||||||
|
(begin
|
||||||
|
(set! prev input)
|
||||||
|
(reducer result input))))))))))
|
||||||
|
|
||||||
|
|
||||||
|
(define* (tdelete-duplicates #:optional (equality-pred? equal?))
|
||||||
|
"tdelede-duplicates is a transducer that deletes any subsequent duplicate
|
||||||
|
elements. Comparisons is done using @var{equality-pred?}, which defaults
|
||||||
|
to @code{equal?}."
|
||||||
|
(lambda (reducer)
|
||||||
|
(let ((already-seen (srfi69:make-hash-table equality-pred?)))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(if (srfi69:hash-table-exists? already-seen input)
|
||||||
|
result
|
||||||
|
(begin
|
||||||
|
(srfi69:hash-table-set! already-seen input #t)
|
||||||
|
(reducer result input))))))))
|
||||||
|
|
||||||
|
(define (tsegment n)
|
||||||
|
"Return a transducer that partitions the input into
|
||||||
|
lists of @var{n} items. If the input stops it flushes any
|
||||||
|
accumulated state, which may be shorter than @var{n}."
|
||||||
|
(if (not (and (integer? n) (positive? n)))
|
||||||
|
(error "argument to tsegment must be a positive integer")
|
||||||
|
(lambda (reducer)
|
||||||
|
(let ((i 0)
|
||||||
|
(collect (make-vector n)))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result)
|
||||||
|
;; if there is anything collected when we are asked to quit
|
||||||
|
;; we flush it to the remaining transducers
|
||||||
|
(let ((result
|
||||||
|
(if (zero? i)
|
||||||
|
result
|
||||||
|
(reducer result (vector->list collect 0 i)))))
|
||||||
|
(set! i 0)
|
||||||
|
;; now finally, pass it downstreams
|
||||||
|
(if (reduced? result)
|
||||||
|
(reducer (unreduce result))
|
||||||
|
(reducer result))))
|
||||||
|
((result input)
|
||||||
|
(vector-set! collect i input)
|
||||||
|
(set! i (+ i 1))
|
||||||
|
;; If we have collected enough input we can pass it on downstream
|
||||||
|
(if (< i n)
|
||||||
|
result
|
||||||
|
(let ((next-input (vector->list collect 0 i)))
|
||||||
|
(set! i 0)
|
||||||
|
(reducer result next-input)))))))))
|
||||||
|
|
||||||
|
(define (tpartition f)
|
||||||
|
"Return a transducer that partitions any input by whenever
|
||||||
|
@code{(f input)} changes value. "
|
||||||
|
(lambda (reducer)
|
||||||
|
(let* ((prev nothing)
|
||||||
|
(collect '()))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result)
|
||||||
|
(let ((result
|
||||||
|
(if (null? collect)
|
||||||
|
result
|
||||||
|
(reducer result (reverse! collect)))))
|
||||||
|
(set! collect '())
|
||||||
|
(if (reduced? result)
|
||||||
|
(reducer (unreduce result))
|
||||||
|
(reducer result))))
|
||||||
|
((result input)
|
||||||
|
(let ((fout (f input)))
|
||||||
|
(cond
|
||||||
|
((or (equal? fout prev) (nothing? prev)) ; collect
|
||||||
|
(set! prev fout)
|
||||||
|
(set! collect (cons input collect))
|
||||||
|
result)
|
||||||
|
(else ; flush what we collected already to the reducer
|
||||||
|
(let ((next-input (reverse! collect)))
|
||||||
|
(set! prev fout)
|
||||||
|
(set! collect (list input))
|
||||||
|
(reducer result next-input))))))))))
|
||||||
|
|
||||||
|
(define (tadd-between elem)
|
||||||
|
"Return a transducer that interposes @var{elem} between each value pushed
|
||||||
|
through the transduction."
|
||||||
|
(lambda (reducer)
|
||||||
|
(let ((send-elem? #f))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result)
|
||||||
|
(reducer result))
|
||||||
|
((result input)
|
||||||
|
(if send-elem?
|
||||||
|
(let ((result (reducer result elem)))
|
||||||
|
(if (reduced? result)
|
||||||
|
result
|
||||||
|
(reducer result input)))
|
||||||
|
(begin
|
||||||
|
(set! send-elem? #t)
|
||||||
|
(reducer result input))))))))
|
||||||
|
|
||||||
|
(define* (tenumerate #:optional (n 0))
|
||||||
|
"Return a transducer that indexes every value passed through into a cons
|
||||||
|
pair as @code{(index . value)}. Starts at @var{n} which defaults to 0."
|
||||||
|
(lambda (reducer)
|
||||||
|
(let ((n n))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(let ((input (cons n input)))
|
||||||
|
(set! n (+ n 1))
|
||||||
|
(reducer result input)))))))
|
||||||
|
|
||||||
|
(define* (tlog #:optional
|
||||||
|
(log-function (lambda (result input) (write input) (newline))))
|
||||||
|
(lambda (reducer)
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result) (reducer result))
|
||||||
|
((result input)
|
||||||
|
(log-function result input)
|
||||||
|
(reducer result input)))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
65
module/srfi/srfi-171/gnu.scm
Normal file
65
module/srfi/srfi-171/gnu.scm
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
;; Copyright (C) 2020 Free Software Foundation, Inc.
|
||||||
|
;;
|
||||||
|
;; This library is free software; you can redistribute it and/or
|
||||||
|
;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;; License as published by the Free Software Foundation; either
|
||||||
|
;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;
|
||||||
|
;; This library is distributed in the hope that it will be useful,
|
||||||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;; Lesser General Public License for more details.
|
||||||
|
;;
|
||||||
|
;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;; License along with this library; if not, write to the Free Software
|
||||||
|
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
(define-module (srfi srfi-171 gnu)
|
||||||
|
#:use-module (srfi srfi-171)
|
||||||
|
#:use-module (srfi srfi-171 meta)
|
||||||
|
#:export (tbatch tfold))
|
||||||
|
|
||||||
|
|
||||||
|
(define tbatch
|
||||||
|
(case-lambda
|
||||||
|
((reducer)
|
||||||
|
(tbatch identity reducer))
|
||||||
|
((t r)
|
||||||
|
(lambda (reducer)
|
||||||
|
(let ((cur-reducer (t r))
|
||||||
|
(cur-state (r)))
|
||||||
|
(case-lambda
|
||||||
|
(() (reducer))
|
||||||
|
((result)
|
||||||
|
(if (equal? cur-state (cur-reducer))
|
||||||
|
(reducer result)
|
||||||
|
(let ((new-res (reducer result (cur-reducer cur-state))))
|
||||||
|
(if (reduced? new-res)
|
||||||
|
(reducer (unreduce new-res))
|
||||||
|
(reducer new-res)))))
|
||||||
|
((result value)
|
||||||
|
(let ((val (cur-reducer cur-state value)))
|
||||||
|
(cond
|
||||||
|
;; cur-reducer is done. Push value downstream
|
||||||
|
;; re-instantiate the state and the cur-reducer
|
||||||
|
((reduced? val)
|
||||||
|
(let ((unreduced-val (unreduce val)))
|
||||||
|
(set! cur-reducer (t r))
|
||||||
|
(set! cur-state (cur-reducer))
|
||||||
|
(reducer result (cur-reducer unreduced-val))))
|
||||||
|
(else
|
||||||
|
(set! cur-state val)
|
||||||
|
result))))))))))
|
||||||
|
|
||||||
|
|
||||||
|
(define* (tfold reducer #:optional (seed (reducer)))
|
||||||
|
(lambda (r)
|
||||||
|
(let ((state seed))
|
||||||
|
(case-lambda
|
||||||
|
(() (r))
|
||||||
|
((result) (r result))
|
||||||
|
((result value)
|
||||||
|
(set! state (reducer state value))
|
||||||
|
(if (reduced? state)
|
||||||
|
(reduced (reducer (unreduce state)))
|
||||||
|
(r result state)))))))
|
113
module/srfi/srfi-171/meta.scm
Normal file
113
module/srfi/srfi-171/meta.scm
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
;; Copyright (C) 2020 Free Software Foundation, Inc.
|
||||||
|
;;
|
||||||
|
;; This library is free software; you can redistribute it and/or
|
||||||
|
;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;; License as published by the Free Software Foundation; either
|
||||||
|
;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;
|
||||||
|
;; This library is distributed in the hope that it will be useful,
|
||||||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;; Lesser General Public License for more details.
|
||||||
|
;;
|
||||||
|
;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;; License along with this library; if not, write to the Free Software
|
||||||
|
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
|
||||||
|
(define-module (srfi srfi-171 meta)
|
||||||
|
#:use-module (srfi srfi-9)
|
||||||
|
#:use-module ((rnrs bytevectors) #:select (bytevector-length bytevector-u8-ref))
|
||||||
|
#:export (reduced reduced?
|
||||||
|
unreduce
|
||||||
|
ensure-reduced
|
||||||
|
preserving-reduced
|
||||||
|
|
||||||
|
list-reduce
|
||||||
|
vector-reduce
|
||||||
|
string-reduce
|
||||||
|
bytevector-u8-reduce
|
||||||
|
port-reduce
|
||||||
|
generator-reduce))
|
||||||
|
|
||||||
|
|
||||||
|
;; A reduced value is stops the transduction.
|
||||||
|
(define-record-type <reduced>
|
||||||
|
(reduced val)
|
||||||
|
reduced?
|
||||||
|
(val unreduce))
|
||||||
|
|
||||||
|
(define (ensure-reduced x)
|
||||||
|
"Ensure that @var{x} is reduced"
|
||||||
|
(if (reduced? x)
|
||||||
|
x
|
||||||
|
(reduced x)))
|
||||||
|
|
||||||
|
;; helper function that wraps a reduced value twice since reducing functions (like list-reduce)
|
||||||
|
;; unwraps them. tconcatenate is a good example: it re-uses it's reducer on it's input using list-reduce.
|
||||||
|
;; If that reduction finishes early and returns a reduced value, list-reduce would "unreduce"
|
||||||
|
;; that value and try to continue the transducing process.
|
||||||
|
(define (preserving-reduced reducer)
|
||||||
|
(lambda (a b)
|
||||||
|
(let ((return (reducer a b)))
|
||||||
|
(if (reduced? return)
|
||||||
|
(reduced return)
|
||||||
|
return))))
|
||||||
|
|
||||||
|
;; This is where the magic tofu is cooked
|
||||||
|
(define (list-reduce f identity lst)
|
||||||
|
(if (null? lst)
|
||||||
|
identity
|
||||||
|
(let ((v (f identity (car lst))))
|
||||||
|
(if (reduced? v)
|
||||||
|
(unreduce v)
|
||||||
|
(list-reduce f v (cdr lst))))))
|
||||||
|
|
||||||
|
(define (vector-reduce f identity vec)
|
||||||
|
(let ((len (vector-length vec)))
|
||||||
|
(let loop ((i 0) (acc identity))
|
||||||
|
(if (= i len)
|
||||||
|
acc
|
||||||
|
(let ((acc (f acc (vector-ref vec i))))
|
||||||
|
(if (reduced? acc)
|
||||||
|
(unreduce acc)
|
||||||
|
(loop (+ i 1) acc)))))))
|
||||||
|
|
||||||
|
(define (string-reduce f identity str)
|
||||||
|
(let ((len (string-length str)))
|
||||||
|
(let loop ((i 0) (acc identity))
|
||||||
|
(if (= i len)
|
||||||
|
acc
|
||||||
|
(let ((acc (f acc (string-ref str i))))
|
||||||
|
(if (reduced? acc)
|
||||||
|
(unreduce acc)
|
||||||
|
(loop (+ i 1) acc)))))))
|
||||||
|
|
||||||
|
(define (bytevector-u8-reduce f identity vec)
|
||||||
|
(let ((len (bytevector-length vec)))
|
||||||
|
(let loop ((i 0) (acc identity))
|
||||||
|
(if (= i len)
|
||||||
|
acc
|
||||||
|
(let ((acc (f acc (bytevector-u8-ref vec i))))
|
||||||
|
(if (reduced? acc)
|
||||||
|
(unreduce acc)
|
||||||
|
(loop (+ i 1) acc)))))))
|
||||||
|
|
||||||
|
(define (port-reduce f identity reader port)
|
||||||
|
(let loop ((val (reader port)) (acc identity))
|
||||||
|
(if (eof-object? val)
|
||||||
|
acc
|
||||||
|
(let ((acc (f acc val)))
|
||||||
|
(if (reduced? acc)
|
||||||
|
(unreduce acc)
|
||||||
|
(loop (reader port) acc))))))
|
||||||
|
|
||||||
|
(define (generator-reduce f identity gen)
|
||||||
|
(let loop ((val (gen)) (acc identity))
|
||||||
|
(if (eof-object? val)
|
||||||
|
acc
|
||||||
|
(let ((acc (f acc val)))
|
||||||
|
(if (reduced? acc)
|
||||||
|
(unreduce acc)
|
||||||
|
(loop (gen) acc))))))
|
||||||
|
|
|
@ -160,6 +160,7 @@ SCM_TESTS = tests/00-initial-env.test \
|
||||||
tests/srfi-98.test \
|
tests/srfi-98.test \
|
||||||
tests/srfi-105.test \
|
tests/srfi-105.test \
|
||||||
tests/srfi-111.test \
|
tests/srfi-111.test \
|
||||||
|
tests/srfi-171.test \
|
||||||
tests/srfi-4.test \
|
tests/srfi-4.test \
|
||||||
tests/srfi-9.test \
|
tests/srfi-9.test \
|
||||||
tests/statprof.test \
|
tests/statprof.test \
|
||||||
|
|
267
test-suite/tests/srfi-171.test
Normal file
267
test-suite/tests/srfi-171.test
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
;; Copyright (C) 2020 Free Software Foundation, Inc.
|
||||||
|
;;
|
||||||
|
;; This library is free software; you can redistribute it and/or
|
||||||
|
;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;; License as published by the Free Software Foundation; either
|
||||||
|
;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;
|
||||||
|
;; This library is distributed in the hope that it will be useful,
|
||||||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;; Lesser General Public License for more details.
|
||||||
|
;;
|
||||||
|
;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;; License along with this library; if not, write to the Free Software
|
||||||
|
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
(define-module (test-srfi-171)
|
||||||
|
#:use-module (test-suite lib)
|
||||||
|
#:use-module (ice-9 hash-table)
|
||||||
|
#:use-module (srfi srfi-171)
|
||||||
|
#:use-module (srfi srfi-171 gnu)
|
||||||
|
#:use-module (rnrs bytevectors)
|
||||||
|
#:use-module ((rnrs hashtables) #:prefix rnrs:)
|
||||||
|
#:use-module ((srfi srfi-69) #:prefix srfi:))
|
||||||
|
|
||||||
|
(define (add1 x) (+ x 1))
|
||||||
|
|
||||||
|
(define numeric-list (iota 5))
|
||||||
|
(define numeric-vec (list->vector numeric-list))
|
||||||
|
(define bv (list->u8vector numeric-list))
|
||||||
|
(define test-string "0123456789abcdef")
|
||||||
|
(define list-of-chars (string->list test-string))
|
||||||
|
|
||||||
|
;; for testing all treplace variations
|
||||||
|
(define replace-alist '((1 . s) (2 . c) (3 . h) (4 . e) (5 . m)))
|
||||||
|
(define guile-hashtable (alist->hash-table replace-alist))
|
||||||
|
(define srfi69-hashtable (srfi:alist->hash-table replace-alist))
|
||||||
|
(define rnrs-hashtable (rnrs:make-eq-hashtable))
|
||||||
|
(rnrs:hashtable-set! rnrs-hashtable 1 's)
|
||||||
|
(rnrs:hashtable-set! rnrs-hashtable 2 'c)
|
||||||
|
(rnrs:hashtable-set! rnrs-hashtable 3 'h)
|
||||||
|
(rnrs:hashtable-set! rnrs-hashtable 4 'e)
|
||||||
|
(rnrs:hashtable-set! rnrs-hashtable 5 'm)
|
||||||
|
(define (replace-function val)
|
||||||
|
(case val
|
||||||
|
((1) 's)
|
||||||
|
((2) 'c)
|
||||||
|
((3) 'h)
|
||||||
|
((4) 'e)
|
||||||
|
((5) 'm)
|
||||||
|
(else val)))
|
||||||
|
|
||||||
|
;; Test procedures for port-transduce
|
||||||
|
;; broken out to properly close port
|
||||||
|
(define (port-transduce-test)
|
||||||
|
(let* ((port (open-input-string "0 1 2 3 4"))
|
||||||
|
(res (equal? 15 (port-transduce (tmap add1) + read
|
||||||
|
(open-input-string "0 1 2 3 4")))))
|
||||||
|
(close-port port)
|
||||||
|
res))
|
||||||
|
(define (port-transduce-with-identity-test)
|
||||||
|
(let* ((port (open-input-string "0 1 2 3 4"))
|
||||||
|
(res (equal? 15 (port-transduce (tmap add1)
|
||||||
|
+
|
||||||
|
0
|
||||||
|
read
|
||||||
|
(open-input-string "0 1 2 3 4")))))
|
||||||
|
(close-port port)
|
||||||
|
res))
|
||||||
|
|
||||||
|
(with-test-prefix "transducers"
|
||||||
|
(pass-if "tmap" (equal? '(1 2 3 4 5) (list-transduce (tmap add1)
|
||||||
|
rcons
|
||||||
|
numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "tfilter" (equal? '(0 2 4) (list-transduce (tfilter even?)
|
||||||
|
rcons
|
||||||
|
numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "tfilter+tmap" (equal?
|
||||||
|
'(1 3 5)
|
||||||
|
(list-transduce (compose (tfilter even?) (tmap add1))
|
||||||
|
rcons
|
||||||
|
numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "tfilter-map"
|
||||||
|
(equal? '(1 3 5)
|
||||||
|
(list-transduce (tfilter-map
|
||||||
|
(lambda (x)
|
||||||
|
(if (even? x)
|
||||||
|
(+ x 1)
|
||||||
|
#f)))
|
||||||
|
rcons numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "tremove"
|
||||||
|
(equal? (list-transduce (tremove char-alphabetic?)
|
||||||
|
rcount
|
||||||
|
list-of-chars)
|
||||||
|
(string-transduce (tremove char-alphabetic?)
|
||||||
|
rcount
|
||||||
|
test-string)))
|
||||||
|
|
||||||
|
(pass-if "treplace with alist"
|
||||||
|
(equal? '(s c h e m e r o c k s)
|
||||||
|
(list-transduce (treplace replace-alist)
|
||||||
|
rcons
|
||||||
|
'(1 2 3 4 5 4 r o c k s) )))
|
||||||
|
|
||||||
|
(pass-if "treplace with replace-function"
|
||||||
|
(equal? '(s c h e m e r o c k s)
|
||||||
|
(list-transduce (treplace replace-function)
|
||||||
|
rcons
|
||||||
|
'(1 2 3 4 5 4 r o c k s))))
|
||||||
|
|
||||||
|
|
||||||
|
(pass-if "treplace with guile hash-table"
|
||||||
|
(equal? '(s c h e m e r o c k s)
|
||||||
|
(list-transduce (treplace guile-hashtable)
|
||||||
|
rcons
|
||||||
|
'(1 2 3 4 5 4 r o c k s))))
|
||||||
|
|
||||||
|
(pass-if "treplace with srfi-69 hash-table"
|
||||||
|
(equal? '(s c h e m e r o c k s)
|
||||||
|
(list-transduce (treplace srfi69-hashtable)
|
||||||
|
rcons
|
||||||
|
'(1 2 3 4 5 4 r o c k s))))
|
||||||
|
|
||||||
|
(pass-if "treplace with rnrs hash-table"
|
||||||
|
(equal? '(s c h e m e r o c k s)
|
||||||
|
(list-transduce (treplace rnrs-hashtable)
|
||||||
|
rcons
|
||||||
|
'(1 2 3 4 5 4 r o c k s))))
|
||||||
|
|
||||||
|
(pass-if "ttake"
|
||||||
|
(equal? 6 (list-transduce (ttake 4) + numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "tdrop"
|
||||||
|
(equal? 7 (list-transduce (tdrop 3) + numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "tdrop-while"
|
||||||
|
(equal? '(3 4)
|
||||||
|
(list-transduce (tdrop-while (lambda (x) (< x 3)))
|
||||||
|
rcons
|
||||||
|
numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "ttake-while"
|
||||||
|
(equal? '(0 1 2)
|
||||||
|
(list-transduce (ttake-while (lambda (x) (< x 3)))
|
||||||
|
rcons
|
||||||
|
numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "tconcatenate"
|
||||||
|
(equal? '(0 1 2 3 4) (list-transduce tconcatenate
|
||||||
|
rcons
|
||||||
|
'((0 1) (2 3) (4)))))
|
||||||
|
|
||||||
|
(pass-if "tappend-map"
|
||||||
|
(equal? '(1 2 2 4 3 6)
|
||||||
|
(list-transduce (tappend-map (lambda (x) (list x (* x 2))))
|
||||||
|
rcons
|
||||||
|
'(1 2 3))))
|
||||||
|
|
||||||
|
(pass-if "tdelete-neighbor-duplicates"
|
||||||
|
(equal? '(1 2 1 2 3)
|
||||||
|
(list-transduce (tdelete-neighbor-duplicates)
|
||||||
|
rcons
|
||||||
|
'(1 1 1 2 2 1 2 3 3))))
|
||||||
|
|
||||||
|
(pass-if "tdelete-neighbor-duplicates with equality predicate"
|
||||||
|
(equal? '(a b c "hej" "hej")
|
||||||
|
(list-transduce (tdelete-neighbor-duplicates eq?)
|
||||||
|
rcons
|
||||||
|
(list 'a 'a 'b 'c 'c "hej" (string #\h #\e #\j)))))
|
||||||
|
|
||||||
|
(pass-if "tdelete-duplicates"
|
||||||
|
(equal? '(1 2 3 4)
|
||||||
|
(list-transduce (tdelete-duplicates)
|
||||||
|
rcons
|
||||||
|
'(1 1 2 1 2 3 3 1 2 3 4))))
|
||||||
|
|
||||||
|
(pass-if "tdelete-duplicates with predicate"
|
||||||
|
(equal? '("hej" "hopp")
|
||||||
|
(list-transduce (tdelete-duplicates string-ci=?)
|
||||||
|
rcons
|
||||||
|
(list "hej" "HEJ" "hopp" "HOPP" "heJ"))))
|
||||||
|
|
||||||
|
(pass-if "tflatten"
|
||||||
|
(equal? '(1 2 3 4 5 6 7 8 9)
|
||||||
|
(list-transduce tflatten rcons '((1 2) 3 (4 (5 6) 7) 8 (9)))))
|
||||||
|
|
||||||
|
(pass-if "tpartition"
|
||||||
|
(equal? '((1 1 1 1) (2 2 2 2) (3 3 3) (4 4 4 4))
|
||||||
|
(list-transduce (tpartition even?)
|
||||||
|
rcons
|
||||||
|
'(1 1 1 1 2 2 2 2 3 3 3 4 4 4 4))))
|
||||||
|
|
||||||
|
(pass-if "tsegment"
|
||||||
|
(equal? '((0 1) (2 3) (4))
|
||||||
|
(vector-transduce (tsegment 2) rcons numeric-vec)))
|
||||||
|
|
||||||
|
(pass-if "tadd-between"
|
||||||
|
(equal? '(0 and 1 and 2 and 3 and 4)
|
||||||
|
(list-transduce (tadd-between 'and) rcons numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "tenumerate"
|
||||||
|
(equal? '((-1 . 0) (0 . 1) (1 . 2) (2 . 3) (3 . 4))
|
||||||
|
(list-transduce (tenumerate (- 1)) rcons numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "tbatch"
|
||||||
|
(equal?
|
||||||
|
'((0 1) (2 3) (4))
|
||||||
|
(list-transduce (tbatch (ttake 2) rcons) rcons numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "tfold"
|
||||||
|
(equal?
|
||||||
|
'(0 1 3 6 10)
|
||||||
|
(list-transduce (tfold +) rcons numeric-list))))
|
||||||
|
|
||||||
|
|
||||||
|
(with-test-prefix "x-transduce"
|
||||||
|
(pass-if "list-transduce"
|
||||||
|
(equal? 15 (list-transduce (tmap add1) + numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "list-transduce with identity"
|
||||||
|
(equal? 15 (list-transduce (tmap add1) + 0 numeric-list)))
|
||||||
|
|
||||||
|
(pass-if "vector-transduce"
|
||||||
|
(equal? 15 (vector-transduce (tmap add1) + numeric-vec)))
|
||||||
|
|
||||||
|
(pass-if "vector-transduce with identity"
|
||||||
|
(equal? 15
|
||||||
|
(vector-transduce (tmap add1) + 0 numeric-vec)))
|
||||||
|
|
||||||
|
(pass-if "port-transduce" (port-transduce-test))
|
||||||
|
(pass-if "port-transduce with identity" (port-transduce-with-identity-test))
|
||||||
|
|
||||||
|
;; Converts each numeric char to it's corresponding integer and sums them.
|
||||||
|
(pass-if "string-transduce"
|
||||||
|
(equal?
|
||||||
|
15
|
||||||
|
(string-transduce (tmap (lambda (x) (- (char->integer x) 47))) + "01234")))
|
||||||
|
|
||||||
|
(pass-if "string-transduce with identity"
|
||||||
|
(equal?
|
||||||
|
15
|
||||||
|
(string-transduce (tmap (lambda (x) (- (char->integer x) 47)))
|
||||||
|
+
|
||||||
|
0
|
||||||
|
"01234")))
|
||||||
|
|
||||||
|
(pass-if "generator-transduce"
|
||||||
|
(equal?
|
||||||
|
'(1 2 3)
|
||||||
|
(parameterize ((current-input-port (open-input-string "1 2 3")))
|
||||||
|
(generator-transduce (tmap (lambda (x) x)) rcons read))))
|
||||||
|
|
||||||
|
(pass-if "generator-transduce with identity"
|
||||||
|
(equal?
|
||||||
|
'(1 2 3)
|
||||||
|
(parameterize ((current-input-port (open-input-string "1 2 3")))
|
||||||
|
(generator-transduce (tmap (lambda (x) x)) rcons '() read))))
|
||||||
|
|
||||||
|
(pass-if "bytevector-u8-transduce"
|
||||||
|
(equal? 15 (bytevector-u8-transduce (tmap add1) + bv)))
|
||||||
|
|
||||||
|
(pass-if "bytevector-u8-transduce with identity"
|
||||||
|
(equal? 15 (bytevector-u8-transduce (tmap add1) + 0 bv))))
|
Loading…
Add table
Add a link
Reference in a new issue