235: Combinators

by John Cowan (text), Arvydas Silanskas (code)

Status

This SRFI is currently in draft status. Here is an explanation of each status that a SRFI can hold. To provide input on this SRFI, please send email to srfi-235@nospamsrfi.schemers.org. To subscribe to the list, follow these instructions. You can access previous messages via the mailing list archive.

Abstract

This SRFI contains various procedures that accept and return procedures, as well as a few others, drawn from an earlier version of Chicken. Common Lisp has a few of them too, and more come from the Standard Prelude from Programming Praxis.

Rationale

Many of these procedures are documented in the style of SRFI 219. Rather than showing only how the procedures themselves are invoked, it also shows how the returned procedures would be invoked. This is done in order to make the descriptions easier to understand. For example, if complement were documented in the standard style, the description would say "Returns a procedure which, when applied to an argument, returns #t when proc would return #f when applied to the same argument, and #f otherwise", which is more convoluted and harder to understand. However, this is merely a documentation style; it would be pointless to actually invoke these procedures in this fashion.

Issues

None at present.

Specification

((constantly obj ...) arg ...)

Returns the objs as its values, ignoring args.

(map (constantly 3) '(1 2 3)) => (3 3 3)

((complement proc) obj)

Returns #t when (proc obj) returns #f, and #f otherwise.

(map (complement (lambda (x) #f)) '(1 2 3)) => (#t #t #t)
(map (complement (lambda (x) 3)) '(1 2 3)) => (#f #f #f)

((flip proc) . objs)

Returns what (apply proc (reverse objs ) returns.

((flip list) 1 2 3) => (3 2 1)

((swap proc) obj₁ obj₂)

Returns (proc obj₂ obj₁).

((swap cons) 1 2) => (2 . 1)

((on-left proc) obj₁ obj₂)

Returns (proc obj₁).

(map (on-left list) '(1 2 3) '(4 5 6)) => '((1) (2) (3))

((on-right proc) obj₁ obj₂)

Returns (proc obj₂).

(map (on-right list) '(1 2 3) '(4 5 6)) => '((4) (5) (6))

((conjoin predicate ...) arg ...)

Returns #t if the args satisfy all the predicates, and #f otherwise. If a predicate is not satisfied, no more predicates are invoked.

(map (conjoin even? exact?) '(1.0 1 2.0 2)) => '(#f #f #f #t)

((disjoin predicate ...) arg ...)

Returns #t if the args satisfy any of the predicates, and #f otherwise. If a predicate is satisfied, no other predicates are invoked.

(map (disjoin even? exact?) '(1.0 1 2.0 2)) => '(#f #t #t #t)

((each-of proc ... ) arg ...)

Applies each of the procs in turn to args, discarding the results and returning an unspecified value.

((all-of predicate) list)

(define (print-sum . numbers)
  (display
    (string-join (map number->string numbers)
                 "+" 'infix))
  (newline))

(define (print-product . numbers)
  (display
    (string-join (map number->string numbers)
                 "*" 'infix))
  (newline))

((each-of print-sum print-product) 1 2 3) => undefined ;prints:
1+2+3
1*2*3

((all-of predicate) list)

Applies predicate to each element of list in turn, and immediately returns #f if predicate is not satisfied by that element; otherwise returns the result of the last call to predicate. If list is empty, returns #t.

((all-of even?) '(2 4 6)) => #t
((all-of odd?) '(1 2 3)) => #f

((some-of predicate) list)

Applies predicate to each element of list in turn, and if predicate is satisfied by that element, immediately returns the result of calling predicate; otherwise returns #f. If list is empty, returns #f.

((some-of even?) '(2 4 5)) => #t
((some-of odd?) '(2 4 6)) => #f

((on reducer mapper) obj ...)

Applies mapper to each obj in any order and then applies reducer to all of the results.

((on + -) 1 2 3) => -6

((left-section proc arg ...) obj ...)

Applies proc to args concatenated with objs.

(map (left-section - 1) '(1 2 3)) => (0 -1 -2)

((right-section proc arg ...) obj ...)

Applies proc to objs concatenated with the value of (reverse args).

(map (right-section - 1) '(1 2 3)) => (0 1 2)

((apply-chain proc ...) arg ...)`

Applies the last proc to args returning zero or more values, then applies the previous proc to the values, returning more values, until the first proc has been invoked; its values are returned. For example, (apply-chain car cdr) returns a procedure that behaves like cadr:

(map (apply-chain car cdr) '((1 2 3) (4 5 6))) => (2 5)

((arguments-drop proc n) arg ...)
((arguments-drop-right proc n) arg ...)
((arguments-take proc n) arg ...)
((arguments-take-right proc n) arg ...)

Apply proc to the args after taking/dropping n arguments from args.

(apply (arguments-drop + 2) '(1 2 3 4 5)) => 12
(apply (arguments-drop-right + 2) '(1 2 3 4 5)) => 6
(apply (arguments-take + 2) '(1 2 3 4 5)) => 3
(apply (arguments-take-right + 2) '(1 2 3 4 5)) => 9

((group-by key-proc [=]) list)

Takes the elements of list and applies key-proc to each of them to get their keys. Elements whose keys are the same (in the sense of =, which defaults to equal?) are grouped into newly allocated lists, and a list of these lists is returned. Within each list, the elements appear in the same order as they do in list; in addition, the first elements of each list also appear in the same order as they do in list. If list is the empty list, it is returned.

((group-by car) '((1 2 3) (2 2 3) (1 4 5))) =>
  (((1 2 3) (1 4 5))
   ((2 2 3))

Syntax-like procedures

These are Scheme procedures that correspond to basic syntax. As usual in Lisps, thunk means a procedure that does not require arguments. The following procedures are understood to be defined in this section:

  (define (a) (display #\a))
  (define (b) (display #\b))
  (define (c) (display #\c)))

(begin-procedure thunk ...)

Invokes thunks in order, and returns what the last thunk returns, or an unspecified value if there are no thunks.

(begin-procedure a b c) => unspecified (displays "abc")

(if-procedure value then-thunk else-thunk)

If value is true, invokes then-thunk and returns what it returns. Otherwise, invokes else-thunk and returns what it returns.

(if-procedure #t (lambda () 1) (lambda () 2)) => 1
(if-procedure #f (lambda () 1) (lambda () 2)) => 2

(when-procedure value thunk ...)
(unless-procedure value thunk ...)

If value is false/true, immediately returns. Otherwise, invokes each thunk in turn and then returns. n all cases an unspecified value is returned.

(when-procedure #t a b c) => unspecified ;prints "abc"
(when-procedure #f a b c) => unspecified ;prints nothing
(unless-procedure #t a b c) => unspecified ;prints nothing
(unless-procedure #f a b c) => unspecified ;prints "abc"

(value-procedure value then-proc else-thunk)

If value is true, invokes then-proc on it and returns what then-proc returns. Otherwise, invokes else-thunk and returns what it returns.

(value-procedure 2 (lambda (x) (+ x 1)) (lambda () 0)) => 3)
(value-procedure #f (lambda (x) (+ x 1)) (lambda () 0)) => 0)

(case-procedure value thunk-alist else-thunk)

Searches thunk-alist for value (as if by assv). If there is a matching entry in thunk-alist, its cdr is invoked as a thunk, and case-procedure returns what the thunk returns. If there is no such entry in thunk-alist, invokes else-thunk and returns what it returns.

(case-procedure 2 `((1 . ,a) (2 . ,b) (3 . ,c))) => undefined ;displays "b"

(lazy-and-procedure thunk ...)

The thunks are invoked from left to right, and if any thunk returns false, then #f is returned. Any remaining thunks are not invoked. If all the thunks return true values, the values of the last thunk are returned. If there are no thunks, then #t is returned.

(lazy-and-procedure
  (lambda () #t)
  (lambda () #f)
  (lambda () (error "fail"))) => #f

(eager-and-procedure thunk ...)

All the thunks are invoked from left to right. If any thunk returns false, then #f is returned. If all the thunks return true values, the value of the last thunk are returned. If there are no thunks, then #t is returned.

(eager-and-procedure
  (lambda () #t)
  (lambda () #f)
  (lambda () (error "fail"))) => error

(lazy-or-procedure thunk ...)

The thunks are invoked from left to right, and the first true value is returned. Any remaining thunks are not invoked. If all thunks return #f or if there are no thunks, then #f is returned.

(lazy-or-procedure
  (lambda () #t)
  (lambda () #f)
  (lambda () (error "fail"))) => #t

(eager-or-procedure thunk ...)

All the thunks are invoked from left to right, and then the first true value is returned. If all thunks return #f or if there are no thunks, then #f is returned.

(eager-or-procedure
  (lambda () #t)
  (lambda () #f)
  (lambda () (error "fail"))) => error

(loop-procedure thunk)

Invokes thunk repeatedly. Does not return unless via call/cc.

(loop-procedure (lambda () (a) (b))) => ; #displays "abab..." forever

(while-procedure thunk)

Invokes thunk repeatedly until it returns false. Returns an unspecified value.

(let ((i 0))
    (while-procedure
      (lambda ()
	(display i)
	(set! i (+ i 1))
	(< i 10)))) => unspecified ;prints "0123456789"

(until-procedure thunk)

Invokes thunk repeatedly until it returns true. Returns an unspecified value.

(let ((i 10))
  (while-procedure
    (lambda ()
      (set! i (- i 1))
      (display i)
       (> i 0)))) => unspecified ;prints "9876543210"

Other procedures

(always obj ...)

Ignores its arguments and always returns #t.

(always 1 2 3) => #t

(never obj ...)

Ignores its arguments and always returns #f.

(never 1 2 3) => #f

(boolean obj)

If obj is true, returns #t; otherwise returns #f.

(boolean 3) => #t
(boolean #f) => #f

(identity obj)

Returns obj; normally passed to a higher-order procedure rather than being invoked directly. Equivalent to values with a single argument but with a clearer name.

(map identity '(1 2 3)) => (1 2 3)

Implementation

The sample implementation is in the SRFI 235 repository.

Acknowledgements

Thanks to the members of the SRFI-235 mailing list.

© 2022 John Cowan, Arvydas Silanskas.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Arthur A. Gleckler