表題

シグニチャと残余引数が可能な互換性のある let フォーム

著者

Andy Gaynor

状態

この SRFI は現在「確定」の状態である。 SRFI の各状態の説明については ここ を参照せよ。 この SRFI に関する議論については メーリングリストのアーカイブ を参照せよ。

概要

let フォームの変形である名前付き let は、2 つの点で define フォームと整合性がない。 定義上、let フォームには残余引数 (rest arguments) を指定することができず、機能性と整合性に問題がある。 定義上、let フォームはシグニチャ スタイルの構文を受け付けず、審美的および整合性の面で問題がある。 ここではこの 2 つの問題に対して、伝統的な let フォームと互換性のある方法で解決を試みるが、これは些細な拡張機能である。

論拠

シグニチャ スタイルの構文

次の 2 つの等価な定義について考えてみる。

(define fibonacci
  (lambda (n i f0 f1)
    (if (= i n)
      f0
      (fibonacci n (+ i 1) f1 (+ f0 f1)))))

(define (fibonacci n i f0 f1)
  (if (= i n)
    f0
    (fibonacci n (+ i 1) f1 (+ f0 f1))))
前者のフォームに類似する名前付き let 文を記述することはできるが、後者のフォームに対してはそれはできない。たとえば、名前付き let を使用してフィボナッチ数列の 10 番目の要素を計算する場合、次のように書ける。

(let fibonacci ((n 10) (i 0) (f0 0) (f1 1))
  (if (= i n)
    f0
    (fibonacci n (+ i 1) f1 (+ f0 f1))))
Values: 55
しかし、現状では次のようなコードを書くことはできない。

(let (fibonacci (n 10) (i 0) (f0 0) (f1 1))
  (if (= i n)
    f0
    (fibonacci n (+ i 1) f1 (+ f0 f1))))
これは define のシグニチャ スタイルのフォームに対応するものである。

シグニチャ スタイルを好む人は、この拡張機能を気に入るだろう。 いずれにしても、すべての束縛名を束縛セクションにまとめてしまうほうがより適切であろう。 ここに示したように、この簡単な拡張によって既存の let の定義との間であいまいさや非互換性が生まれることはない。

残余引数

現状では、次のように名前付き let を残余引数とともに使用することはできない。

(let (blast (port (current-output-port)) . (x (+ 1 2) 4 5))
  (if (null? x)
    'just-a-silly-contrived-example
    (begin
      (write (car x) port)
      (apply blast port (cdr x)))))
その代わり次のように書く。

(letrec ((blast (lambda (port . x)
      (if (null? x)
          'just-a-silly-contrived-example
          (begin
      (write (car x) port)
      (apply blast port (cdr x)))))))
  (blast (current-output-port) (+ 1 2) 4 5))
この例は不自然な例ではあるが、機能自体はよく使われるものである。 著者は実践においてこのようなコードを書くことが何度かあった。 let フォームがラムダ式のすべての機能を使用することを否定する理由はほとんどない。

束縛セクション内のシンボル

上記の 2 つの機能はともに let の束縛リスト内のシンボルの位置によって決まる。このようなシンボルの位置をもつ別の場合として、変数を不定値に束縛するための簡易な方法がある。たとえば、(let (foo bar baz) ...) とすると、foobarbaz は不定値に束縛される。

上記の論拠を踏まえれば、このような使用法は重要性が低く、(let ((foo) (bar) (baz)) ...) のような別の構文を使えばよいことは明らかであろう。むしろこちらの構文のほうが、通常の束縛節を括弧で囲むので好ましいものといえる。

仕様

構文

構文の公式仕様を以下に示す。ここで body、expression、identifier は任意である。binding-name の各インスタンスは一意でなければならない。

            let = "(" "let" let-bindings body ")"
    expressions = nothing | expression expressions
   let-bindings = let-name bindings
                | "(" let-name "." bindings ")"
       let-name = identifier
       bindings = "(" ")"
                | rest-binding
                | "(" normal-bindings ["." rest-binding] ")"
normal-bindings = nothing
                | normal-binding normal-bindings
 normal-binding = "(" binding-name expression ")"
   binding-name = identifier
   rest-binding = "(" binding-name expressions ")"

明瞭さと簡便さのために、非公式の仕様を以下に示す。

  1. 名前なし

    (let ((<parameter> <argument>)...)
      <body>...)
    
  2. 名前付き、非シグニチャ スタイル、残余引数なし

    (let <name> ((<parameter> <argument>)...)
      <body>...)
    
  3. 名前付き、シグニチャ スタイル、残余引数なし

    (let (<name> (<parameter> <argument>)...)
      <body>...)
    
  4. 名前付き、非シグニチャ スタイル、残余引数あり

    (let <name> ((<parameter> <argument>)...
    
    . (<rest-parameter> <rest-argument>...))
      <body>...)
    
  5. 名前付き、シグニチャ スタイル、残余引数あり

    (let (<name> (<parameter> <argument>)...
    
    . (<rest-parameter> <rest-argument>...))
      <body>...)
    

意味論

$lambda$letrec をそれぞれ lambda フォームと letrec フォームのためのハイジニックな束縛とする。

実装

以下に SYNTAX-RULES を使用した実装を与える。

;; Use your own standard let.
;; Or call a lambda.
;; (define-syntax standard-let
;;
;;   (syntax-rules ()
;;
;;     ((let ((var val) ...) body ...)
;;      ((lambda (var ...) body ...) val ...))))

(define-syntax let

  (syntax-rules ()

    ;; No bindings: use standard-let.
    ((let () body ...)
     (standard-let () body ...))
    ;; Or call a lambda.
    ;; ((lambda () body ...))

    ;; All standard bindings: use standard-let.
    ((let ((var val) ...) body ...)
     (standard-let ((var val) ...) body ...))
    ;; Or call a lambda.
    ;; ((lambda (var ...) body ...) val ...)

    ;; One standard binding: loop.
    ;; The all-standard-bindings clause didn't match,
    ;; so there must be a rest binding.
    ((let ((var val) . bindings) body ...)
     (let-loop #f bindings (var) (val) (body ...)))

    ;; Signature-style name: loop.
    ((let (name binding ...) body ...)
     (let-loop name (binding ...) () () (body ...)))

    ;; defun-style name: loop.
    ((let name bindings body ...)
     (let-loop name bindings () () (body ...)))))

(define-syntax let-loop

  (syntax-rules ()

    ;; Standard binding: destructure and loop.
    ((let-loop name ((var0 val0) binding ...) (var ...     ) (val ...     ) body)
     (let-loop name (            binding ...) (var ... var0) (val ... val0) body))

    ;; Rest binding, no name: use standard-let, listing the rest values.
    ;; Because of let's first clause, there is no "no bindings, no name" clause.
    ((let-loop #f (rest-var rest-val ...) (var ...) (val ...) body)
     (standard-let ((var val) ... (rest-var (list rest-val ...))) . body))
    ;; Or call a lambda with a rest parameter on all values.
    ;; ((lambda (var ... . rest-var) . body) val ... rest-val ...))
    ;; Or use one of several other reasonable alternatives.

    ;; No bindings, name: call a letrec'ed lambda.
    ((let-loop name () (var ...) (val ...) body)
     ((letrec ((name (lambda (var ...) . body)))
        name)
      val ...))

    ;; Rest binding, name: call a letrec'ed lambda.
    ((let-loop name (rest-var rest-val ...) (var ...) (val ...) body)
     ((letrec ((name (lambda (var ... . rest-var) . body)))
        name)
      val ... rest-val ...))))

著作権

Copyright (C) Andy Gaynor (1999). All Rights Reserved.

This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works. However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Scheme Request For Implementation process or editors, except as needed for the purpose of developing SRFIs in which case the procedures for copyrights defined in the SRFI process must be followed, or as required to translate it into languages other than English.

The limited permissions granted above are perpetual and will not be revoked by the authors or their successors or assigns.

This document and the information contained herein is provided on an "AS IS" basis and THE AUTHOR AND THE SRFI EDITORS DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.


編集者: Mike Sperber