chickadee » transducers » collect-first

collect-first #!optional (sentinel #f)procedure
collect-last #!optional (sentinel #f)procedure

A collector that returns either the first or last item of the collection only. first will terminate early after collecting the first item.

 
(import transducers)

(transduce list-fold
           (filter odd?)
           (collect-first)
           (list 2 4 6 8 11 13 1 2 4))

; => 11

(transduce list-fold
           (filter odd?)
           (collect-last)
           (list 2 4 6 8 11 13 1 2 4))

; => 1

collect-first in particular is very useful as a collector. By combining the collect-first collector and the filter transducer, we can implement a "find" operation on a collection:

 
(import transducers)

(transduce list-fold
           (filter (lambda (x) (eq? x 'c)))
           (collect-first)
           (list 'b 'c 'd 'c))

; => 'c

What's more, collect-first and collect-last are also very useful with sentinel values. If collect-first or collect-last fail to find any items (say, because you filtered them out), then usually they return false:

 
(import transducers)

(transduce list-fold
           (filter odd?)
           (collect-first)
           (list 2 4 6 8))

; => #f

This is not too dissimilar from most find operations in Scheme. However, #f can sometimes be ambiguous. If you were extracting the value from an a-list as follows:

 
(import transducers)

(transduce list-fold
           (compose
             (filter (lambda (kv-pair)
                       (eq? (car kv-pair) 'bar)))
             (map cdr))
           (collect-first)
           (list (cons 'foo 1)
                 (cons 'bar #f)
                 (cons 'baz "abcd")))

; => #f

then we can see that #f is ambiguous. Instead, we can use a custom sentinel value, for example the sentinel (nothing) from SRFI-189:

 
(import transducers srfi-189)

(transduce list-fold
           (compose
             (filter (lambda (kv-pair)
                       (eq? (car kv-pair) 'bar)))
             (map cdr))
           (collect-first)
           (list (cons 'foo 1)
                 (cons 'bar #f)
                 (cons 'baz "abcd")))

; => #f

(transduce list-fold
           (compose
             (filter (lambda (kv-pair)
                       (eq? (car kv-pair) 0)))
             (map cdr))
           (collect-first (nothing))
           (list (cons 'foo 1)
                 (cons 'bar #f)
                 (cons 'baz "abcd")))

; => #<srfi-189#<nothing>>

This way, one can use the custom sentinel value to distinguish between ambiguous #f returns.