Title

ALAMBDA and ALAMBDA*

Author

Joo ChurlSoo

Status

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

Abstract

This SRFI introduces ALAMBDA and ALAMBDA*, each of which has two modes of operation:

  1. it creates a procedure that checks actual arguments and takes optional arguments,
  2. it returns a different procedure by checking each of actual arguments and the number of them.

Note: the name and contents of this SRFI have been changed, to reflect that CHECK-LAMBDA, and COND-LAMBDA were combined into the new ALAMBDA.

Rationale

The first mode of operation reduces not only the clutter of various error conditionals by checking actual arguments but also somewhat lengthy code by combining optional argument handling methods such as LET-OPTIONALS and LET-KEYWORDS into a single syntax.

Optional variables include not only optional fixed variables but also optional non-fixed variables. The formers are the same as those of opt form of ALET and the latters are the same as those of cat and key forms of ALET (see SRFI-86). The following are examples to show the similarities.

1. optional fixed variables (opt form):

((lambda (str . rest)
   (alet* ((len (string-length str))
           (opt rest
                (start 0 (and (integer? start) (<= 0 start len)))
                (end len (and (integer? end) (<= start end len)))))
     (substring str start end))) "abcdefg" 1 6)
=> "bcdef"

((alambda* (str
            "opt"
            (start 0 (and (integer? start) (<= 0 start (string-length str))))
            (end (string-length str)
                 (and (integer? end) (<= start end (string-length str)))))
   (substring str start end)) "abcdefg" 1 6)
=> "bcdef"

2. optional non-fixed non-named variables (cat form):

((lambda (str . rest)
   (alet ((cat rest
               (start 0
                      (and (list? start) (= 2 (length start))
                           (eq? 'start (car start)))
                      (cadr start))     ; true
               (end (string-length str)
                    (and (list? end) (= 2 (length end)) (eq? 'end (car end)))
                    (cadr end))))       ; true
     (substring str start end))) "abcdefg" '(end 6) '(start 1))
=> "bcdef"

((alambda* (str
            "cat"
            (start 0
                   (and (list? start) (= 2 (length start))
                        (eq? 'start (car start)))
                   (cadr start))        ; true
            (end (string-length str)
                 (and (list? end) (= 2 (length end)) (eq? 'end (car end)))
                 (cadr end)))           ; true
     (substring str start end)) "abcdefg" '(end 6) '(start 1))
=> "bcdef"

3. optional non-fixed named variables (`key' form):

((lambda (str . rest)
   (alet* ((len (string-length str))
           (key rest
                (start 0 (and (integer? start) (<= 0 start len)))
                (end len (and (integer? end) (<= start end len)))))
     (substring str start end))) "abcdefg" 'end 6 'start 1)
=> "bcdef"

((alambda* (str
            "key"
            (start 0 (and (integer? start) (<= 0 start (string-length str))))
            (end (string-length str)
                 (and (integer? end) (<= start end (string-length str)))))
   (substring str start end)) "abcdefg" 'end 6 'start 1)
=> "bcdef"

Like optional variables, required variables can be divded into three groups, namely, conventional required fixed variables, required non-fixed non-named variables, and required non-fixed named variables. These are best explained by simple examples.

1. required fixed variables:

(define vec-ref
  (alambda* ((vec (vector? vec))
             (num (and (integer? num) (<= 0 num) (< num (vector-length vec)))))
    (vector-ref vec num)))
(vec-ref '#(1 2 3) 1)   => 2
(vec-ref 1 '#(1 2 3))   => bad argument 1 vec (vector? vec)
(vec-ref '#(1 2 3))     => wrong number of arguments

2. required non-fixed non-named variables:

(define str-ref
  (alambda* ("required cat"
             (str (string? str))
             (n (and (integer? n) (<= 0 n) (< n (string-length str)))))
    (string-ref str n)))
(str-ref "abc" 1)   => #\b
(str-ref 1 "abc")   => #\b
(str-ref 1 2)       => bad arguments (1 2) str (string? str)
(str-ref "abc")     => wrong number of arguments

3. required non-fixed named variables:

(define lis-ref
  (alambda* ("required key"
             (lis (list? lis))
             (num (and (integer? num) (<= 0 num) (< num (length lis)))))
    (list-ref lis num)))
(lis-ref 'num 1 'lis '(1 2 3)) => 2
(lis-ref 'lis '(1 2 3) 'num 1) => 2
(lis-ref 'lis '(1 2 3) 'nu 3)  => no corresponding value to key num (nu 3)
(lis-ref 'lis '(1 2 3) 'nu)    => wrong number of arguments

The second mode of operation reduces the clutter of procedures more precisely than CASE-LAMBDA of SRFI-16 by adding <test> to its formal argument list to check each of actual arguments. The following are examples to show the differences.

(define list-ref/set!
  (case-lambda
   ((a b) (list-ref a b))
   ((a b c) (set-car! (list-tail a b) c))
   (a (error "bad arguments" a))))

(define ref/set!
  (alambda (cond
            (((a (list? a)) b) (list-ref a b))
            (((a (string? a)) b) (string-ref a b))
            (((a (vector? a)) b) (vector-ref a b))
            (((a (list? a)) b c) (set-car! (list-tail a b) c))
            (((a (string? a)) b c) (string-set! a b c))
            (((a (vector? a)) b c) (vector-set! a b c))
            (a (error "bad arguments" a)))))

Specification

The syntax is defined in the extended BNF of R5RS.

(alambda  <sentence>)
(alambda* <sentence>)
syntax-rules identifier: cond
<sentence> -->  <extended formals> <body> | (cond <clause>+)
<extended formals> -->
          | (<required spec> <optional spec> <key option>)
          | (<required spec> <key option>)
          | (<opiontal spec> <key option>)
          | (<required spec> <optional spec> <key option> . <variable>)
          | (<required spec> <key option> . <variable>)
          | (<optional spec> <key option> . <variable>)
          | <variable>
          | ()
<required spec> --> <required fixed variable spec>+
                  | <required fixed variable spec>* <required non-fixed spec>
<required fixed variable spec> --> <variable> | (<variable> <test spec>)
<test spec> --> <test>
              | <test> <true substitute>
              | <test> <true substitute> <false substitute>
<required non-fixed spec> --> <required cat spec>
                            | <required key spec>
                            | <required cat spec> <required key spec>
                            | <required key spec> <required cat spec>
<required cat spec> --> "required cat" <required fixed variable spec>+
<required key spec> --> "required key" <required key variable spec>+
<required key variable spec> --> <required fixed variable spec>
                               | ((<variable> <keyword>))
                               | ((<variable> <keyword>) <test spec>)
<optional spec> --> <opt spec>
                  | <cat spec>
                  | <key spec>
                  | <opt spec> <cat spec>
                  | <opt spec> <key spec>
                  | <cat spec> <key spec>
                  | <key spec> <cat spec>
                  | <opt spec> <cat spec> <key spec>
                  | <opt spec> <key spec> <cat spec>
<opt spec> --> "opt" <opt variable spec>+
<opt variable spec> --> <variable>
                      | (<variable> <default>)
                      | (<variable> <default> <test spec>)
<cat spec> --> "cat" <opt variable spec>+
<key spec> --> "key" <key variable spec>+
<key variable spec> --> <opt variable spec>
                      | ((<variable> <keyword>))
                      | ((<variable> <keyword>) <default>)
                      | ((<variable> <keyword>) <default> <test spec>)
<key option> --> #f | #t | <empty>
<test> --> <expression>
<true substitute> --> <expression>
<false substitute> --> <expression>
<default> --> <expression>
<keyword> --> <any scheme object>

<clause> --> (<cond formals> <body>)
<cond formals> --> (<cond variable spec>*)
                 | <variable>
                 | (<cond variable spec>+ . <variable>)
<cond variable spec> --> <variable>
                       | (<variable> <test>)

The ALAMBDA* is to the ALAMBDA what the LET* is to the LET. The <default>s, <test>s, <true substitute>s, and <false substitute>s of ALAMBDA* are evaluated in an environment that all the bindings of previous <variable>s are visible.

1. the first mode of operation:

There are three kinds of required variables, namely, required fixed variable, required non-fixed non-named variable, and required non-fixed named variable. They determine the number of required actual arguments, that is, the minimum arity of the resulting procedure.

The required fixed variables are placed before any string marker in <extended formals>. They are bound to successive actual arguments starting with the first actual argument. If there is a <test>, it is evaluated. If it returns a false value and there is no <false substitute>, an error is signaled. If it returns a false value and there is a <false substitute>, the variable is rebound to the result of evaluating <false substitute> instead of signaling an error. If it returns a true value and there is a <true substitute>, the variable is rebound to the result of evaluating <true substitute>.

The required non-fixed non-named variables follow a "required cat" marker in <extended formals>. The variable is temporarily bound to each of remaining required actual arguments sequentially, until <test> returns a true value, then the variable is finally bound to the passed argument. If there is no <test>, the first one of the remaining required actual arguments is regarded as passing. If any actual argument does not pass <test>, an error is signaled. If there is a <false substitute> and <test> returns a false value, the variable is finally bound to the result of evaluating <false substitute> instead of the above process. If there is a <true substitute> and <test> returns a true value, the variable is rebound to the result of evaluating <true substitute>.

The required non-fixed named variables follow a "required key" marker in <extended formals>. Unlike SRFI-89, the keywords are not self-evaluating symbols, but any scheme objects. The keyword used at a call site for the corresponding variable is a symbol of the same name as the variable. But the keyword can be any scheme object when the required parameter is specified as a double parenthesized variable and a keyword. The remaining required actual arguments must be an even number. They are sequentially interpreted as a series of pairs, where the first member of each pair is a keyword corresponding to the variable, and the second is the corresponding value. If there is no element for a particular keyword, an error is signaled. When there is a <test>, it is evaluated. If it returns a false value and there is no <false substitute>, an error is signaled. If it returns a false value and there is a <false substitute>, the variable is rebound to the result of evaluating <false substitute> instead of signaling an error. If it returns a true value and there is a <true substitute>, the variable is rebound to the result of evaluating <true substitute>.

The optional fixed variables follow an "opt" marker in <extended formals>. The binding method is the same as that of the required fixed variables except that the variable is bound to the result of evaluating <default> instead of signaling an error if there are no remaining actual arguments. If <default> is not specified, #f is the default.

The optional non-fixed non-named variables follow a "cat" marker in <extended formals>. The binding method is the same as that of the required non-fixed non-named variables except that the variable is bound to the result of evaluating <default> instead of signaling an error if any actual argument does not pass <test>. If <default> is not specified, #f is the default.

The optional non-fixed named variables follow a "key" marker in <extended formals>. The binding method is the same as that of the required non-fixed named variables except that the variable is bound to the result of evaluating <default> instead of signaling an error if there is no corresponding value to a particular keyword. If <default> is not specified, #f is the default.

The following key options can be used to control binding behavior in case that the keyword of keyword-value pair at the call site is different from any keywords specified in <extended formals>.

  1. default -- The remaining actual arguments are continually interpreted as a series of pairs.
  2. #f -- An error is signaled in case of required non-fixed named variables. In case of optional non-fixed named variables, the variable is bound to the corresponding <default>.
  3. #t -- The remaining actual arguments are continually interpreted as a single element until the element is a particular keyword.

When there are remaining actual arguments, an error is signaled if dotted rest variable is not given. If dotted rest variable is given, it is bound to the remaining actual arguments.

2. the second mode of operation:

This is an extended form of CASE-LAMBDA of SRFI-16. Like CASE-LAMBDA, it returns a procedure of the first <clause>, the <cond formals> of which is matched with the number of actual arguments. But if there is a <test> and the result of evaluation returns a false value, the subsequent <clause> is processed in spite of the match. If no <clause> matches, an error is signaled.

Examples

((alambda (a
           (b (number? b))
           "opt" (c 10)
           "key" ((d 'dd) 30 (number? d)) e ((f "ff") 40) (g 50)
           . h)
   (list a b c d e f g h))
 0 1 2 'dd 3 44 55 'g 6 'dd 4 'f 66 77 "ff" 5)
=> (0 1 2 3 #f 40 6 (44 55 dd 4 f 66 77 "ff" 5))

((alambda (a
           (b (number? b))
           "opt" (c 10)
           "key" ((d 'dd) 30 (number? d)) e ((f "ff") 40) (g 50)
           #f
           . h)
   (list a b c d e f g h))
 0 1 2 'dd 3 44 55 'g 6 'dd 4 'f 66 77 "ff" 5)
=> (0 1 2 3 #f 40 50 (44 55 g 6 dd 4 f 66 77 "ff" 5))

((alambda (a
           (b (number? b))
           "opt" (c 10)
           "key" ((d 'dd) 30 (number? d)) e ((f "ff") 40) (g 50)
           #t
           . h)
   (list a b c d e f g h))
 0 1 2 'dd 3 44 55 'g 6 'dd 4 'f 66 77 "ff" 5)
=> (0 1 2 3 #f 5 6 (44 55 dd 4 f 66 77))

(define ref/set!
  (alambda* (cond
             (((a (list? a))
               (b (and (integer? b) (<= 0 b) (< b (length a)))))
              (list-ref a b))
             (((a (string? a))
               (b (and (integer? b) (<= 0 b) (< b (string-length a)))))
              (string-ref a b))
             (((a (vector? a))
               (b (and (integer? b) (<= 0 b) (< b (vector-length a)))))
              (vector-ref a b))
             (((a (list? a))
               (b (and (integer? b) (<= 0 b) (< b (length a))))
               c)
              (set-car! (list-tail a b) c))
             (((a (string? a))
               (b (and (integer? b) (<= 0 b) (< b (string-length a))))
               (c (char? c)))
              (string-set! a b c))
             (((a (vector? a))
               (b (and (integer? b) (<= 0 b) (< b (vector-length a))))
               c)
              (vector-set! a b c))
             (a (error "bad arguments" a)))))

(let ((str (string #\a #\b #\c))
      (lis (list 1 2 3))
      (vec (vector 4 5 6)))
  (display (ref/set! str 1)) (display " ")
  (display (ref/set! lis 1)) (display " ")
  (display (ref/set! vec 1)) (newline)
  (display str) (display " ")
  (display lis) (display " ")
  (display vec) (newline)
  (ref/set! str 1 #\z) (ref/set! lis 1 8) (ref/set! vec 1 9)
  (display str) (display " ")
  (display lis) (display " ")
  (display vec) (newline))
=> b 2 5
   abc (1 2 3) #(4 5 6)
   azc (1 8 3) #(4 9 6)

Implementation

The implementation is written in R5RS hygienic macros and requires SRFI-23 (Error reporting mechanism).

References

[R5RS]      Richard Kelsey, William Clinger, and Jonathan Rees: Revised(5)
            Report on the Algorithmic Language Scheme
            http://www.schemers.org/Documents/Standards/R5Rs/
[SRFI 16]   Lars T Hansen: Syntax for procedures of variable arity.
            https://srfi.schemers.org/srfi-16/
[SRFI 86]   Joo ChurlSoo: MU and NU simulating VALUES & CALL-WITH-VALUES,
            and their related LET-syntax.
            https://srfi.schemers.org/srfi-86/
[SRFI 89]   Marc Feeley: Optional and named parameters.
            https://srfi.schemers.org/srfi-89/

Copyright

Copyright (c) 2006 Joo ChurlSoo.

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 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: Francisco Solsona