From 87a74d106f11680c4924befb664d7ef685c16b06 Mon Sep 17 00:00:00 2001 From: Colin Woodbury Date: Thu, 22 Dec 2022 20:32:33 +0900 Subject: [PATCH 4/4] doc: added a guide for writing custom reducers The guide previously explained what reducers were, but not the specifics of how to write them yourself. This commits rectifies this. --- doc/ref/srfi-modules.texi | 76 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/doc/ref/srfi-modules.texi b/doc/ref/srfi-modules.texi index 6eb1a563e..e3beb2ff5 100644 --- a/doc/ref/srfi-modules.texi +++ b/doc/ref/srfi-modules.texi @@ -5966,6 +5966,82 @@ Yield the maximum (or minimum) value of the transduction, or the @var{seed} value if there is none. @end deffn +@subheading Writing your own reducers +If you want to reduce some values via an ordinary function that you +already have on-hand, then @code{rfold} is all you need: + +@example +;; Same as rmax. +(list-transduce (tmap identity) (rfold max 0) '(1 2 3 4 5)) +@result{} 5 +@end example + +However, if you want more customized behaviour (such as early +termination and/or arbitrary manipulation of the input values) then +you're free to write a reducer manually. To do so, we need to write a +function that can accept one to three arguments. We do this with +@code{case-lambda}. Let's create a simple reducer that adds all the +numbers in its input: + +@example +(define rsum + (case-lambda + ;; This is the first case called. It establishes the on-going + ;; accumulator value. + (() 0) + ;; This is the last case called. Its result is the final output + ;; of the reduction. + ((result-so-far) result-so-far) + ;; This is the normal case. We do something to the accumulator + ;; and the current input. + ((result-so-far input) (+ result-so-far input))) + +(list-transduce (tfilter odd?) rsum '(1 2 3 4)) +@result{} 4 +@end example + +You'll notice that @code{rsum} isn't actually included in SRFI-171, +because @code{+} already behaves exactly this way. + +@subsubheading Higher-order reducers +Of course, the top-level @code{define} can also take arguments, and we +can use these within the @code{case-lambda}. Here's how @code{rlast} is +implemented: + +@example +(define (rlast seed) + (case-lambda + (() seed) + ((result-so-far) result-so-far) + ((_ input) input))) + +(list-transduce (tfilter odd?) (rlast 0) '(1 2 3 4 5 6)) +@result{} 5 +@end example + +The @code{seed} here acts as a default for when nothing was left in the +transduction. You're free of course to pass in whatever arguments you +wish (including other functions) and use them as you see fit. + +@subsubheading Early termination +Importing @code{(srfi srfi-171 meta)} exposes the @code{reduced} +function, which we can use to signal to the overall transduction process +that we're done, and no further input needs to be pulled from the data +source. Here's @code{rfind} (already included in Guile's SRFI-171 +extensions) which escapes the iteration when some condition is met: + +@example +(define (rfind pred?) + (case-lambda + (() #f) + ((acc) acc) + ((acc input) (if (pred? input) (reduced input) #f)))) +@end example + +Important note: calling @code{reduced} yourself like this does activate +the final results-only branch, so any extra effects or manipulations you +have there will also occur. + @node SRFI-171 Transducers @subsubsection Transducers @cindex transducers transducers -- 2.39.0