let
フォーム
let
フォームの変形である名前付き let は、2 つの点で define
フォームと整合性がない。
定義上、let
フォームには残余引数 (rest arguments) を指定することができず、機能性と整合性に問題がある。
定義上、let
フォームはシグニチャ スタイルの構文を受け付けず、審美的および整合性の面で問題がある。
ここではこの 2 つの問題に対して、伝統的な let
フォームと互換性のある方法で解決を試みるが、これは些細な拡張機能である。
(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 (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
フォームがラムダ式のすべての機能を使用することを否定する理由はほとんどない。
let
の束縛リスト内のシンボルの位置によって決まる。このようなシンボルの位置をもつ別の場合として、変数を不定値に束縛するための簡易な方法がある。たとえば、(let (foo bar baz) ...)
とすると、foo
、bar
、baz
は不定値に束縛される。
上記の論拠を踏まえれば、このような使用法は重要性が低く、(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 ")"
明瞭さと簡便さのために、非公式の仕様を以下に示す。
(let ((<parameter> <argument>)...) <body>...)
(let <name> ((<parameter> <argument>)...) <body>...)
(let (<name> (<parameter> <argument>)...) <body>...)
(let <name> ((<parameter> <argument>)... . (<rest-parameter> <rest-argument>...)) <body>...)
(let (<name> (<parameter> <argument>)... . (<rest-parameter> <rest-argument>...)) <body>...)
$lambda
と $letrec
をそれぞれ lambda
フォームと letrec
フォームのためのハイジニックな束縛とする。
(($lambda (<parameter>...) <body>...) <argument>...)
($letrec ((<name> ($lambda (<parameter>...) <body>...))) (<name> <argument>...))
($letrec ((<name> ($lambda (<parameter>... . <rest-parameter>) <body>...))) (<name> <argument>... <rest-argument>...))
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 ...))))
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.