For Gauche 0.9.5


Next: , Previous: , Up: 基本的な構文   [Contents][Index]

4.3 手続きを作る

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

[R7RS+] この式は評価されると手続きを生成します。この式が評価された時点の環境が手続き中に保持されます。 手続きが呼ばれると、記憶された環境に引数の束縛を追加した環境中で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 は以下のいずれかの形でなければなりません。

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][SRFI-16] それぞれの clause は (formals expr …) という形式 でなければなりません。ここで、formalslambda の仮引数 リストです。

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

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


Next: , Previous: , Up: 基本的な構文   [Contents][Index]