For Development HEAD DRAFTSearch (procedure/syntax/module):

4.3 手続きを作る

Special Form: lambda formals body …
Special Form: ^ formals body …

[R7RS+ base] この式は評価されると手続きを生成します。この式が評価された時点の環境が手続き中に保持されます。 手続きが呼ばれると、記憶された環境に引数の束縛を追加した環境中でbody が順に評価され、 最後の式の値が返されます。

^lambdaの短い別名です。これはGauche独自の拡張です。

(lambda (a b) (+ a b))
  ⇒ 二つの引数を加算する手続き

((lambda (a b) (+ a b)) 1 2) ⇒ 3

((^(a b) (+ a b)) 1 2)       ⇒ 3

Gaucheはまた、lambdaが取るformalsの構文を拡張し、 省略可能引数やキーワード引数を簡単に指定できるようにしています。 同じ機能は純粋なR7RSでも、可変長引数を自力で解析することで実現可能ですが、 コードはより長く冗長になってしまいます。ポータブルなコードを書いているのでなければ 拡張構文を使った方が良いでしょう。

Formals は以下のいずれかの形でなければなりません。

  • (variable …) : 手続きは決まった数の引数を取ります。 実引数がそれぞれ対応するvariableに束縛されます。
    ((lambda (a) a) 1)    ⇒ 1
    
    ((lambda (a) a) 1 2)  ⇒ error - wrong number of arguments
    
  • variable : 手続きは不定個の引数を取ります。 実引数は新しいリストに集められて、そのリストがvariableに束縛されます。
    ((lambda a a) 1 2 3)  ⇒ (1 2 3)
    
  • (variable_0variable_N-1 . variable_N) : 手続きは少なくともN個の引数を取ります。N個までの実引数は対応するvariable に束縛されます。N個を越えた実引数は新しいリストに集められて variable_Nに束縛されます。
    ((lambda (a b . c) (print "a=" a " b=" b " c=" c)) 1 2 3 4 5)
     ⇒ prints a=1 b=2 c=(3 4 5)
    
  • (variableextended-spec …) : 拡張引数指定です。ゼロ個以上の必須引数を示す変数のリストの後に、 キーワード:optional:keyあるいは:restで 始まるextended-specが続きます。

    extended-specは省略可能引数指定、キーワード引数指定、残余引数指定の 任意の組み合わせにより構成されます。

    :optional optspec

    省略可能引数を指定します。各optspecは以下のいずれかの形式です。

    variable
    (variable init-expr)
    

    仮引数variableは、対応する実引数が与えられればその値に、 与えられなければinit-exprを評価した値に束縛されます。 optspecvariableだけである場合は、 実引数が省略された時の仮引数の値は#<undef>になります (未定義値参照)。

    init-exprは対応する実引数が与えられなかった場合にのみ評価されます。 init-exprの評価環境にはこのoptspecに先立つ仮引数が含まれます。

    ((lambda (a b :optional (c (+ a b))) (list a b c))
     1 2)    ⇒ (1 2 3)
    
    ((lambda (a b :optional (c (+ a b))) (list a b c))
     1 2 -1) ⇒ (1 2 -1)
    
    ((lambda (a b :optional c) (list a b c))
     1 2)    ⇒ (1 2 #<undef>)
    
    ((lambda (:optional (a 0) (b (+ a 1))) (list a b))
     )       ⇒ (0 1)
    

    必須引数と省略可能引数の総数よりも多い実引数が渡された場合、 その手続きが:key:rest引数指定を持っていない限りは エラーが通知されます。

    ((lambda (:optional a b) (list a b)) 1 2 3)
     ⇒ error - too many arguments
    
    ((lambda (:optional a b :rest r) (list a b r)) 1 2 3)
     ⇒ (1 2 (3))
    
    :key keyspec … [:allow-other-keys [variable]]

    キーワード引数を指定します。各keyspecは以下の形式のいずれかです。

    variable
    (variable init-expr)
    ((keyword variable) init-expr)
    

    仮引数variableは、実引数にvariableと同名のキーワードによる キーワード引数が与えられればその値に、そうでなければinit-exprを評価 した値に束縛されます。init-exprの無い最初の形式では、実引数が 与えられなければvariable#<undef>に束縛されます。

    (define f (lambda (a :key (b (+ a 1)) (c (+ b 1)))
                (list a b c)))
    
    (f 10)            ⇒ (10 11 12)
    (f 10 :b 4)       ⇒ (10 4 5)
    (f 10 :c 8)       ⇒ (10 11 8)
    (f 10 :c 1 :b 3)  ⇒ (10 3 1)
    

    三番目の形式では、仮引数の名前とは別に引数を与えるキーワードを指定できます。

    ((lambda (:key ((:aa a) -1)) a) :aa 2)
      ⇒ 2
    

    デフォルトでは、キーワード引数指定をもつ手続きは、認識できないキーワード引数が 与えられた場合にエラーを通知します。仮引数リストに:allow-other-keysを指定することで、 この動作を抑制することができます。 :allow-other-keysに続いてvariableを与えれば、 認識できなかったキーワード引数のリストがvariableに束縛されます。

    ((lambda (:key a) a)
     :a 1 :b 2)  ⇒ error - unknown keyword :b
    
    ((lambda (:key a :allow-other-keys) a)
     :a 1 :b 2)  ⇒ 1
    
    ((lambda (:key a :allow-other-keys z) (list a z))
     :a 1 :b 2)  ⇒ (1 (:b 2))
    

    :optional引数指定と同時に使われた場合、キーワード引数はすべての 省略可能引数が束縛された後の実引数リストから探されます。

    ((lambda (:optional a b :key c) (list a b c))
     1 2 :c 3)  ⇒ (1 2 3)
    
    ((lambda (:optional a b :key c) (list a b c))
     :c 3)      ⇒ (:c 3 #<undef>)
    
    ((lambda (:optional a b :key c) (list a b c))
     1 :c 3)     ⇒ error - keyword list not even
    
    :rest variable

    残余引数を指定します。:optional引数指定が無い場合は、 必須引数の束縛が済んだ後で残っている実引数のリストがvariableに 束縛されます。:optional引数指定がある場合は、 必須引数とすべての省略可能引数の束縛が済んだ後で残っている実引数の リストがvariableに束縛されます。

    ((lambda (a b :rest z) (list a b z))
     1 2 3 4 5)  ⇒ (1 2 (3 4 5))
    
    ((lambda (a b :optional c d :rest z) (list a b c d z))
     1 2 3 4 5)  ⇒ (1 2 3 4 (5))
    
    ((lambda (a b :optional c d :rest z) (list a b c d z))
     1 2 3)      ⇒ (1 2 3 #<undef> ())
    

    残余引数指定とキーワード引数指定の両方が与えられた場合、 両者はともに、必須引数と省略可能引数が処理された後の実引数リストを処理の対象とします。

    ((lambda (:optional a :rest r :key k) (list a r k))
     1 :k 3)  ⇒ (1 (:k 3) 3)
    

    R7RSの仕様範囲内で省略可能引数、キーワード引数を受け取るように 書くこともできます。省略可能引数のパージングlet-optionals*let-keywordslet-keywords*を参照してください。

Macro: ^c body …

(lambda (c) body …)の短縮表記です。 cには#[_a-z]に含まれる任意の一文字が使えます。

(map (^x (* x x)) '(1 2 3 4 5)) ⇒ (1 4 9 16 25)
Macro: cut expr-or-slot expr-or-slot2 …
Macro: cute expr-or-slot expr-or-slot2 …

[SRFI-26] 手続きを簡潔に書ける便利なマクロです。 いわゆる部分適用を実現するために使えます。

expr-or-slotは式またはシンボル<>でなければなりません。 最後のexpr-or-slotはシンボル<...>であっても構いません。 cutは、そのフォーム中にある<>と同じ数の引数を取る lambdaフォームに展開されます。そのフォームのボディには次の式が置かれます。

  (expr-or-slot expr-or-slot2 ...)

但し、各<>は対応する仮引数に置き換えられます。 もしシンボル<...>が与えられていた場合、展開されたlambdaフォームは 可変長引数を取る手続きとなり、作成される手続きに与えられたすべての引数が expr-or-slotを呼び出すのに使われます(下の4番目の例を参照のこと)。

(cut cons (+ a 1) <>)  ≡ (lambda (x2) (cons (+ a 1) x2))
(cut list 1 <> 3 <> 5) ≡ (lambda (x2 x4) (list 1 x2 3 x4 5))
(cut list)             ≡ (lambda () (list))
(cut list 1 <> 3 <...>)
   ≡ (lambda (x2 . xs) (apply list 1 x2 3 xs))
(cut <> a b)           ≡ (lambda (f) (f a b))

;; Usage
(map (cut * 2 <>) '(1 2 3 4))
(for-each (cut write <> port) exprs)

cutecutとほぼ同じですが、expr-or-slotに与えられた フォームが手続きを作る前に評価される点が異なります。

(cute cons (+ a 1) <>)
   ≡ (let ((xa (+ a 1))) (lambda (x2) (cons xa x2)))

Gaucheには他にも二つほど、部分適用を簡潔に書く方法があります。 下の$マクロ、及び組み込み手続きpa$です (コンビネータ参照)。

Macro: $ arg …

関数適用をチェインするマクロです。Haskellの$にヒントを得ました (意味は異なりますが)。 マクロ引数arg …中に$が出現すると、それが 関数の最後の引数の区切りとなります。例えば次のコードでは、 関数fの最後の引数が(g c d …)となります。

  ($ f a b $ g c d ...)
  ≡ (f a b (g c d ...))

$はチェインすることができます。

  ($ f a b $ g c d $ h e f ...)
  ≡ (f a b (g c d (h e f ...)))

$のかわりに$*が現れた場合は、最後の引数ひとつだけでなく 「残りの引数全部」を示します。

  ($ f a b $* g c d ...)
  ≡ (apply f a b (g c d ...))

  ($ f a b $* g $ h $* hh ...)
  ≡ (apply f a b (g (apply h (hh ...))))

さらに、もし引数リストが$または$*で終わっていた場合は、 式全体が「残りの引数(リスト)」を受け取ることを期待する手続きとなります。

  ($ f a b $ g c d $ h e f $)
  ≡ (lambda (arg) (f a b (g c d (h e f arg))))
  ≡ (.$ (cut f a b <>) (cut g c d <>) (cut h e f <>))

  ($ f a b $ g c d $ h e f $*)
  ≡ (lambda args (f a b (g c d (apply h e f args))))
  ≡ (.$ (cut f a b <>) (cut g c d <>) (cut h e f <...>))

関数的にコードを書いていると、関数呼び出しが深くネストすることが多くなります。 しかしSchemeの構文はそのようなネストとあまり相性が良くありません。 閉じ括弧が式の最後にずらずらと積み重なりますし、また通常のインデントルールでは コードが右のカラムにどんどん伸びていってしまいます。次の同等な二つの式を比べてみてください。

(intersperse ":"
             (map transform-it
                  (delete-duplicates (map cdr
                                          (group-sequence input)))))

($ intersperse ":"
   $ map transform-it
   $ delete-duplicates
   $ map cdr $ group-sequence input)

これはあくまで好みの問題ですし、こういった構文糖衣は濫用されがちなので 気をつけてください。けれどもスパイスのように、控えめな隠し味として使うと、 しばしばとても有用です。

コーナーケースとして、引数リストに$$*も現れなかった場合は、 単なる関数呼び出しになります。これだけでも、関数名が長くて引数をあまり右に 深くインデントしたくない場合には便利です。

  ($ f a b c) ≡ (f a b c)
Macro: case-lambda clause …

[R7RS case-lambda] それぞれの clause は (formals expr …) という形式 でなければなりません。ここで、formalslambda の仮引数 リストです。

この式は、評価されると可変数の引数をとり、lambda 式から結果として できる手続きと同じレキシカルスコープをもつ手続きになります。この手続きが いくつかの引数とともに呼ばれると、その引数が formals と一致する 最初の clause が選択されます。この引数の一致というのは、 lambda 式の formals に対応するものとして指定されるものです。 formals の変数は実引数に束縛され、 expr … がその 環境内で評価されます。

実際の引数がどの clauseformals にも一致しなければエラーです。

(define f
  (case-lambda
    [() 'zero]
    [(a) `(one ,a)]
    [(a b) `(two ,a ,b)]))

(f)       ⇒ zero
(f 1)     ⇒ (one 1)
(f 1 2)   ⇒ (two 1 2)
(f 1 2 3) ⇒ Error: wrong number of arguments to case lambda

(define g
  (case-lambda
    [() 'zero]
    [(a) `(one ,a)]
    [(a . b) `(more ,a ,@b)]))

(g)       ⇒ zero
(g 1)     ⇒ (one 1)
(g 1 2 3) ⇒ (more 1 2 3)

clauseは順に引数の個数とマッチするかどうか調べられます。次の例では g2(one ...)を返すことはありません。

(define g2
  (case-lambda
    [() 'zero]
    [(a . b) `(more ,a ,@b)]
    [(a) `(one ,a)]))

(g2 1)    ⇒ (more 1)


For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT