srfi-171.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. ;; Copyright (C) 2020 Free Software Foundation, Inc.
  2. ;;
  3. ;; This library is free software; you can redistribute it and/or
  4. ;; modify it under the terms of the GNU Lesser General Public
  5. ;; License as published by the Free Software Foundation; either
  6. ;; version 3 of the License, or (at your option) any later version.
  7. ;;
  8. ;; This library is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ;; Lesser General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU Lesser General Public
  14. ;; License along with this library; if not, write to the Free Software
  15. ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  16. (define-module (srfi srfi-171)
  17. #:use-module (ice-9 match)
  18. #:use-module (srfi srfi-9)
  19. #:use-module ((srfi srfi-43) #:select (vector->list))
  20. #:use-module ((srfi srfi-69) #:prefix srfi69:)
  21. #:use-module ((rnrs hashtables) #:prefix rnrs:)
  22. #:use-module (srfi srfi-171 meta)
  23. #:export (rcons
  24. reverse-rcons
  25. rcount
  26. rany
  27. revery
  28. list-transduce
  29. vector-transduce
  30. string-transduce
  31. bytevector-u8-transduce
  32. port-transduce
  33. generator-transduce
  34. tmap
  35. tfilter
  36. tremove
  37. treplace
  38. tfilter-map
  39. tdrop
  40. tdrop-while
  41. ttake
  42. ttake-while
  43. tconcatenate
  44. tappend-map
  45. tdelete-neighbor-duplicates
  46. tdelete-duplicates
  47. tflatten
  48. tsegment
  49. tpartition
  50. tadd-between
  51. tenumerate
  52. tlog))
  53. (cond-expand-provide (current-module) '(srfi-171))
  54. ;; A placeholder for a unique "nothing".
  55. (define nothing (list 'nothing))
  56. (define (nothing? val)
  57. (eq? val nothing))
  58. ;;; Reducing functions meant to be used at the end at the transducing process.
  59. (define rcons
  60. (case-lambda
  61. "A transducer-friendly consing reducer with '() as identity."
  62. (() '())
  63. ((lst) (reverse! lst))
  64. ((lst x) (cons x lst))))
  65. (define reverse-rcons
  66. (case-lambda
  67. "A transducer-friendly consing reducer with '() as identity.
  68. The resulting list is in reverse order."
  69. (() '())
  70. ((lst) lst)
  71. ((lst x) (cons x lst))))
  72. (define rcount
  73. (case-lambda
  74. "A counting reducer that counts any elements that made it through the
  75. transduction.
  76. @example
  77. (transduce (tfilter odd?) tcount (list 1 2 3)) @result{} 2
  78. @end example"
  79. (() 0)
  80. ((result) result)
  81. ((result input)
  82. (+ 1 result))))
  83. (define (rany pred)
  84. (case-lambda
  85. "Return a reducer that tests input using @var{pred}. If any input satisfies
  86. @var{pred}, return @code{(reduced value)}."
  87. (() #f)
  88. ((result) result)
  89. ((result input)
  90. (let ((test (pred input)))
  91. (if test
  92. (reduced test)
  93. #f)))))
  94. (define (revery pred)
  95. (case-lambda
  96. "Returns a reducer that tests input using @var{pred}. If any input satisfies
  97. @var{pred}, it returns @code{(reduced #f)}."
  98. (() #t)
  99. ((result) result)
  100. ((result input)
  101. (let ((test (pred input)))
  102. (if (and result test)
  103. test
  104. (reduced #f))))))
  105. (define list-transduce
  106. (case-lambda
  107. ((xform f coll)
  108. (list-transduce xform f (f) coll))
  109. ((xform f init coll)
  110. (let* ((xf (xform f))
  111. (result (list-reduce xf init coll)))
  112. (xf result)))))
  113. (define vector-transduce
  114. (case-lambda
  115. ((xform f coll)
  116. (vector-transduce xform f (f) coll))
  117. ((xform f init coll)
  118. (let* ((xf (xform f))
  119. (result (vector-reduce xf init coll)))
  120. (xf result)))))
  121. (define string-transduce
  122. (case-lambda
  123. ((xform f coll)
  124. (string-transduce xform f (f) coll))
  125. ((xform f init coll)
  126. (let* ((xf (xform f))
  127. (result (string-reduce xf init coll)))
  128. (xf result)))))
  129. (define bytevector-u8-transduce
  130. (case-lambda
  131. ((xform f coll)
  132. (bytevector-u8-transduce xform f (f) coll))
  133. ((xform f init coll)
  134. (let* ((xf (xform f))
  135. (result (bytevector-u8-reduce xf init coll)))
  136. (xf result)))))
  137. (define port-transduce
  138. (case-lambda
  139. ((xform f by)
  140. (generator-transduce xform f by))
  141. ((xform f by port)
  142. (port-transduce xform f (f) by port))
  143. ((xform f init by port)
  144. (let* ((xf (xform f))
  145. (result (port-reduce xf init by port)))
  146. (xf result)))))
  147. (define generator-transduce
  148. (case-lambda
  149. ((xform f gen)
  150. (generator-transduce xform f (f) gen))
  151. ((xform f init gen)
  152. (let* ((xf (xform f))
  153. (result (generator-reduce xf init gen)))
  154. (xf result)))))
  155. ;;; Transducers
  156. (define (tmap f)
  157. (lambda (reducer)
  158. (case-lambda
  159. (() (reducer))
  160. ((result) (reducer result))
  161. ((result input)
  162. (reducer result (f input))))))
  163. (define (tfilter pred)
  164. (lambda (reducer)
  165. (case-lambda
  166. (() (reducer))
  167. ((result) (reducer result))
  168. ((result input)
  169. (if (pred input)
  170. (reducer result input)
  171. result)))))
  172. (define (tremove pred)
  173. (lambda (reducer)
  174. (case-lambda
  175. (() (reducer))
  176. ((result) (reducer result))
  177. ((result input)
  178. (if (not (pred input))
  179. (reducer result input)
  180. result)))))
  181. (define (tfilter-map f)
  182. (compose (tmap f) (tfilter values)))
  183. (define (make-replacer map)
  184. (cond
  185. ((list? map)
  186. (lambda (x)
  187. (match (assoc x map)
  188. ((_ . replacer) replacer)
  189. (#f x))))
  190. ((srfi69:hash-table? map)
  191. (lambda (x)
  192. (srfi69:hash-table-ref/default map x x)))
  193. ((rnrs:hashtable? map)
  194. (lambda (x)
  195. (rnrs:hashtable-ref map x x)))
  196. ((hash-table? map)
  197. (lambda (x)
  198. (hash-ref map x x)))
  199. ((procedure? map) map)
  200. (else
  201. (error "Unsupported mapping in treplace" map))))
  202. (define (treplace map)
  203. "Return a transducer that searches for any input in @var{map}, which may
  204. be a guile native hashtable, an R6RS hashtable, a srfi-69 hashtable, an alist
  205. or a one-argument procedure taking one value and producing either the same
  206. value or a replacement one. Alists and guile-native hashtbles compare keys
  207. using @code{equal?} whereas the other mappings use whatever equality predicate
  208. they were created with."
  209. (tmap (make-replacer map)))
  210. (define (tdrop n)
  211. (lambda (reducer)
  212. (let ((new-n (+ 1 n)))
  213. (case-lambda
  214. (() (reducer))
  215. ((result) (reducer result))
  216. ((result input)
  217. (set! new-n (- new-n 1))
  218. (if (positive? new-n)
  219. result
  220. (reducer result input)))))))
  221. (define (tdrop-while pred)
  222. (lambda (reducer)
  223. (let ((drop? #t))
  224. (case-lambda
  225. (() (reducer))
  226. ((result) (reducer result))
  227. ((result input)
  228. (if (and (pred input) drop?)
  229. result
  230. (begin
  231. (set! drop? #f)
  232. (reducer result input))))))))
  233. (define (ttake n)
  234. (lambda (reducer)
  235. ;; we need to reset new-n for every new transduction
  236. (let ((new-n n))
  237. (case-lambda
  238. (() (reducer))
  239. ((result) (reducer result))
  240. ((result input)
  241. (let ((result (if (positive? new-n)
  242. (reducer result input)
  243. result)))
  244. (set! new-n (- new-n 1))
  245. (if (not (positive? new-n))
  246. (ensure-reduced result)
  247. result)))))))
  248. (define ttake-while
  249. (case-lambda
  250. ((pred) (ttake-while pred (lambda (result input) result)))
  251. ((pred retf)
  252. (lambda (reducer)
  253. (let ((take? #t))
  254. (case-lambda
  255. (() (reducer))
  256. ((result) (reducer result))
  257. ((result input)
  258. (if (and take? (pred input))
  259. (reducer result input)
  260. (begin
  261. (set! take? #f)
  262. (ensure-reduced (retf result input)))))))))))
  263. (define (tconcatenate reducer)
  264. (let ((preserving-reducer (preserving-reduced reducer)))
  265. (case-lambda
  266. (() (reducer))
  267. ((result) (reducer result))
  268. ((result input)
  269. (list-reduce preserving-reducer result input)))))
  270. (define (tappend-map f)
  271. (compose (tmap f) tconcatenate))
  272. (define (tflatten reducer)
  273. "tflatten is a transducer that flattens any list passed through it.
  274. @example
  275. (list-transduce tflatten conj (list 1 2 (list 3 4 '(5 6) 7 8)))
  276. @result{} (1 2 3 4 5 6 7 8)
  277. @end example"
  278. (case-lambda
  279. (() '())
  280. ((result) (reducer result))
  281. ((result input)
  282. (if (list? input)
  283. (list-reduce (preserving-reduced (tflatten reducer)) result input)
  284. (reducer result input)))))
  285. (define tdelete-neighbor-duplicates
  286. (case-lambda
  287. (() (tdelete-neighbor-duplicates equal?))
  288. ((equality-pred?)
  289. (lambda (reducer)
  290. (let ((prev nothing))
  291. (case-lambda
  292. (() (reducer))
  293. ((result) (reducer result))
  294. ((result input)
  295. (if (equality-pred? prev input)
  296. result
  297. (begin
  298. (set! prev input)
  299. (reducer result input))))))))))
  300. (define* (tdelete-duplicates #:optional (equality-pred? equal?))
  301. "tdelede-duplicates is a transducer that deletes any subsequent duplicate
  302. elements. Comparisons is done using @var{equality-pred?}, which defaults
  303. to @code{equal?}."
  304. (lambda (reducer)
  305. (let ((already-seen (srfi69:make-hash-table equality-pred?)))
  306. (case-lambda
  307. (() (reducer))
  308. ((result) (reducer result))
  309. ((result input)
  310. (if (srfi69:hash-table-exists? already-seen input)
  311. result
  312. (begin
  313. (srfi69:hash-table-set! already-seen input #t)
  314. (reducer result input))))))))
  315. (define (tsegment n)
  316. "Return a transducer that partitions the input into
  317. lists of @var{n} items. If the input stops it flushes any
  318. accumulated state, which may be shorter than @var{n}."
  319. (if (not (and (integer? n) (positive? n)))
  320. (error "argument to tsegment must be a positive integer")
  321. (lambda (reducer)
  322. (let ((i 0)
  323. (collect (make-vector n)))
  324. (case-lambda
  325. (() (reducer))
  326. ((result)
  327. ;; if there is anything collected when we are asked to quit
  328. ;; we flush it to the remaining transducers
  329. (let ((result
  330. (if (zero? i)
  331. result
  332. (reducer result (vector->list collect 0 i)))))
  333. (set! i 0)
  334. ;; now finally, pass it downstreams
  335. (if (reduced? result)
  336. (reducer (unreduce result))
  337. (reducer result))))
  338. ((result input)
  339. (vector-set! collect i input)
  340. (set! i (+ i 1))
  341. ;; If we have collected enough input we can pass it on downstream
  342. (if (< i n)
  343. result
  344. (let ((next-input (vector->list collect 0 i)))
  345. (set! i 0)
  346. (reducer result next-input)))))))))
  347. (define (tpartition f)
  348. "Return a transducer that partitions any input by whenever
  349. @code{(f input)} changes value. "
  350. (lambda (reducer)
  351. (let* ((prev nothing)
  352. (collect '()))
  353. (case-lambda
  354. (() (reducer))
  355. ((result)
  356. (let ((result
  357. (if (null? collect)
  358. result
  359. (reducer result (reverse! collect)))))
  360. (set! collect '())
  361. (if (reduced? result)
  362. (reducer (unreduce result))
  363. (reducer result))))
  364. ((result input)
  365. (let ((fout (f input)))
  366. (cond
  367. ((or (equal? fout prev) (nothing? prev)) ; collect
  368. (set! prev fout)
  369. (set! collect (cons input collect))
  370. result)
  371. (else ; flush what we collected already to the reducer
  372. (let ((next-input (reverse! collect)))
  373. (set! prev fout)
  374. (set! collect (list input))
  375. (reducer result next-input))))))))))
  376. (define (tadd-between elem)
  377. "Return a transducer that interposes @var{elem} between each value pushed
  378. through the transduction."
  379. (lambda (reducer)
  380. (let ((send-elem? #f))
  381. (case-lambda
  382. (() (reducer))
  383. ((result)
  384. (reducer result))
  385. ((result input)
  386. (if send-elem?
  387. (let ((result (reducer result elem)))
  388. (if (reduced? result)
  389. result
  390. (reducer result input)))
  391. (begin
  392. (set! send-elem? #t)
  393. (reducer result input))))))))
  394. (define* (tenumerate #:optional (n 0))
  395. "Return a transducer that indexes every value passed through into a cons
  396. pair as @code{(index . value)}. Starts at @var{n} which defaults to 0."
  397. (lambda (reducer)
  398. (let ((n n))
  399. (case-lambda
  400. (() (reducer))
  401. ((result) (reducer result))
  402. ((result input)
  403. (let ((input (cons n input)))
  404. (set! n (+ n 1))
  405. (reducer result input)))))))
  406. (define* (tlog #:optional
  407. (log-function (lambda (result input) (write input) (newline))))
  408. (lambda (reducer)
  409. (case-lambda
  410. (() (reducer))
  411. ((result) (reducer result))
  412. ((result input)
  413. (log-function result input)
  414. (reducer result input)))))