[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.16 制御


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.16.1 手続き

Builtin Class: <procedure>
Function: procedure? obj

[R5RS] objが手続きなら#tを、そうでなければ#fを返します。

Function: apply proc arg1 … args

[R5RS] (arg1 … . args)を引数として手続きprocを呼びます。 最後の引数argsは正規のリストでなければなりません。 procが返す 値をそのまま返します。

 
(apply list 'a 'b '(c d e)) ⇒ (a b c d e)

(apply + 1 2 '(3 4 5))      ⇒ 15

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.16.1.1 マッピング

Function: map proc list1 list2 …

[R5RS+] 与えられたリストの各要素に対してprocを適用し、その結果をリストにして 返します。R5RSではprocの適用順序が定められていませんが、Gaucheでは 常にprocはリスト内の順番で呼ばれます。 複数のリストが与えられた場合、最も短いリストが終了した時点でprocの適用を 打ち切ります。

 
(map car '((a b) (c d) (e f))) ⇒ (a c e)

(map cons '(a b c) '(d e f))
  ⇒ ((a . d) (b . e) (c . f))

gauche.collectionモジュール(gauche.collection - コレクションフレームワーク参照) を使うと、mapがリスト以外のコレクション型に対しても動作するようになります。

Function: for-each proc list1 list2 …

[R5RS] 手続きprocをリストの各エレメントに対して順に適用します。 procの結果は捨てられます。for-eachの戻り値は定義されていません。 複数のリストが与えられた場合、一番短いリストが終了した時点でfor-eachは終了します。

gauche.collectionモジュール(gauche.collection - コレクションフレームワーク参照) を使うと、for-eachがリスト以外のコレクション型に対しても動作するようになります。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.16.1.2 コンビネータ

Gaucheには、combinatory programmingに使えるいくつかの基本手続きがあります。

Function: pa$ proc arg …

部分適用。手続きを返します。その手続きが引数m …を伴って 呼ばれた場合、それは(proc arg … m …)と等価になります。

 
(define add3 (pa$ + 3))
(add3 4) ⇒ 7

(map (pa$ * 2) '(1 2 3)) ⇒ (2 4 6)

SRFI-26で定義されているマクロcutcuteも似たような抽象化の 方法を提供しますが、pa$より多少柔軟性が高く、その分やや冗長です。 手続きを作るを参照して下さい。

Function: apply$ proc
Function: map$ proc
Function: for-each$ proc

apply, mapfor-eachの部分適用版です。

 
(define map2* (map$ (pa$ * 2)))
(map2* '(1 2 3)) ⇒ (2 4 6)
Function: count$ pred
Function: fold$ kons &optional knil
Function: fold-right$ kons &optional knil
Function: reduce$ f &optional ridentity
Function: reduce-right$ f &optional ridentity
Function: filter$ pred
Function: remove$ pred
Function: partition$ pred
Function: member$ item
Function: find$ pred
Function: find-tail$ pred
Function: any$ pred
Function: every$ pred
Function: delete$ pred
Function: assoc$ item

SRFI-1(srfi-1 - List library参照)の手続に対応する部分適用版手続。

Function: compose f …

複数の手続きを結合します。引数は全て手続きでなければなりません。 2つの引数が渡された時、(compose f g)は次の式と等価です。

 
(lambda args (call-with-values (lambda () (apply g args)) f))

2つ以上の引数が渡された場合は、次のように結合されます。

 
(compose f g h ...) ≡ (compose (compose f g) h ...)

いくつか例を示します。

 
(define not-zero? (compose not zero?))
(not-zero? 3) ⇒ #t
(not-zero? 0) ⇒ #f

(define dot-product (compose (apply$ +) (map$ *)))
(dot-product '(1 2 3) '(4 5 6)) ⇒ 32

境界のケース:ひとつだけ引数が渡された場合は、その引数がそのまま返されます。 引数が全く渡されなかった場合は手続きvaluesが返されます。

Function: complement pred

述語predの意味を逆にした手続きを返します。すなわち、predが真を 返すような引数にたいして偽を返す、またその逆も同様であるような手続きです。

 
(map (complement even?) '(1 2 3)) ⇒ '(#t #f #t)
(map (complement =) '(1 2 3) '(1 1 3)) ⇒ '(#f #t #f)
((complement (lambda () #f))) ⇒ #t
Function: any-pred pred …

与えられた引数をそれぞれ述語predに適用する手続きを返します。 いずれかのpred#fでない値を返す場合、その値を返します。 全てのpred#fを返す場合、#fを返します。

 
(define string-or-symbol? (any-pred string? symbol?))
(string-or-symbol? "abc") ⇒ #t
(string-or-symbol? 'abc)  ⇒ #t
(string-or-symbol? 3)     ⇒ #f

(define <> (any-pred < >))
(<> 3 4) ⇒ #t
(<> 3 3) ⇒ #f

((any-pred (cut memq <> '(a b c))
           (cut memq <> '(1 2 3)))
 'b)  ⇒ '(b c)
Function: every-pred pred …

与えられた引数をそれぞれ述語predに適用する手続きを返します。 全てのpred#fでない値を返す場合、戻り値は最後の predの戻り値になります。いずれかのpred#fを 返す場合、every-predはそれ以降のpredを呼び出さずに #fを返します。

 
((every-pred odd? positive?) 3)  ⇒ #t
((every-pred odd? positive?) 4)  ⇒ #f
((every-pred odd? positive?) -3) ⇒ #f

(define safe-length (every-pred list? length))
(safe-length '(a b c))  ⇒ 3
(safe-length "aaa")     ⇒ #f

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.16.1.3 省略可能引数のパージング

Schemeでオプショナル引数やキーワード引数を使うためには、 可変引数をリストとして取り自分で分解しなければなりません。 以下のマクロがヘルパーとして使えます。

Macro: let-optionals* restargs (var-spec …) body …
Macro: let-optionals* restargs (var-spec … . restvar) body …

与えられた値のリストrestargsを、var-specにしたがって 変数に束縛し、bodyを評価します。

var-specはシンボルか、そのcarがシンボルである2要素のリストの いずれかです。シンボルは束縛された変数名です。 restargsにある値は、順番にシンボルに束縛されます。 restargsvar-specに示される数の値がない場合は、 残りのsymbolは以下に従ってデフォルト値が束縛されます。 var-specが単なるシンボルなら、デフォルト値は未定義です。 var-specがリストなら、デフォルト値はリストの2番目の要素を 評価した結果です。後者の場合、2番目の要素は十分な引数がない場合にのみ 評価されます。 束縛はvar-specの順番にしたがって行われるので、2番目の要素は 以前のvar-specのバインディングを参照するかも知れません。

2番目のフォームでは、restvarはシンボルでなければならず、 var-specに束縛された後、restargsに残っている値のリストに 束縛されます。

restargvar-specよりも多い値を持っていてもエラーでは ありません。最初のフォームでは、余分な値は単に無視されます。

 
(define (proc x . args)
  (let-optionals* args ((a 'a)
                        (b 'b)
                        (c 'c))
    (list x a b c)))

(proc 0)         ⇒ (0 a b c)
(proc 0 1)       ⇒ (0 1 b c)
(proc 0 1 2)     ⇒ (0 1 2 c)
(proc 0 1 2 3)   ⇒ (0 1 2 3)

(define (proc2 . args)
  (let-optionals* args ((a 'a) . b)
    (list a b)))

(proc2)          ⇒ (a ())
(proc2 0)        ⇒ (0 ())
(proc2 0 1)      ⇒ (0 (1))
(proc2 0 1 2)    ⇒ (0 (1 2))

(define (proc3 . args)
  (let-optionals* args ((a 0)
                        (b (+ a 1))
                        (c (+ b 1)))
    (list a b c)))

(proc3)          ⇒ (0 1 2)
(proc3 8)        ⇒ (8 9 10)
(proc3 8 2)      ⇒ (8 2 3)
(proc3 8 2 -1)   ⇒ (8 2 -1)
Macro: get-optional restargs default

これはlet-optionals*の短いバージョンで、オプショナル引数が 1つしかないときに使います。オプショナル引数のリストとしてrestargsが 与えらると、このマクロはオプショナル引数が与えられていればその値を返し、 そうでなければdefaultの結果を返します。defaultrestargsが 空リストでなければ評価されません。

 
(define (proc x . maybe-opt)
  (let ((option (get-optional maybe-opt #f)))
    (list x option)))

(proc 0)         ⇒ (0 #f)
(proc 0 1)       ⇒ (0 1)
Macro: let-keywords restarg (var-spec …) body …
Macro: let-keywords restarg (var-spec … . restvar) body …

このマクロはキーワード引数のためのものです。var-specは 以下のフォームのうちのいずれかです。

(symbol expr)

restargsymbolと同じ名前を持つキーワードを含んでいる場合、 symbolを対応する値に束縛します。そのようなキーワードがrestargに ない場合は、symbolexprの結果に束縛します。

(symbol keyword expr)

restargがキーワードkeywordを含む場合、 symbolを対応する値に束縛します。そのようなキーワードがrestargに ない場合、symbolexprの結果に束縛します。

デフォルト値exprは、restargにキーワードが与えられてなかった 場合にのみ評価されます。

1番目のフォームでは、var-specにないキーワード引数がrestargに 現れるのはエラーです。以前のバージョンとの互換性のために、現在は警告を 表示するだけですが、将来はエラーを通知するよう動作が変わるでしょう。 警告を表示したくない場合は次の2番目のフォームを使ってください。

2番目のフォームでは、restvarはシンボルか#fでなければなりません。 シンボルのときは、var-specに束縛されなかったrestargsのキーワード リストがrestvarに束縛されます。#fのときは、それらのrestargs のキーワードは単に無視されます。

 
(define (proc x . options)
  (let-keywords options ((a 'a)
                         (b :beta 'b)
                         (c 'c)
                         . rest)
    (list x a b c rest)))

(proc 0)         ⇒ (0 a b c ())
(proc 0 :a 1)    ⇒ (0 1 b c ())
(proc 0 :beta 1) ⇒ (0 a 1 c ())
(proc 0 :beta 1 :c 3 :unknown 4) ⇒ (0 a 1 3 (:unknown 4))
Macro: let-keywords* restarg (var-spec …) body …
Macro: let-keywords* restarg (var-spec … . restvar) body …

このマクロはlet-keywordsとほぼ同じですが、束縛がvar-specでの 順番に行われるところが異なります。exprは以前のvar-specにより 束縛された変数を参照できます。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.16.1.4 手続きのアリティ

手続きのアリティを問い合わせるインターフェースです。 APIは、MzScheme (PLT Scheme)を参考にしました。

Function: arity proc

手続きprocを与え、整数、arity-at-leastオブジェクト、 整数とarity-at-leastオブジェクトからなるリストのいずれかを 返します。

整数の戻り値は、procが正確にその数の引数を取ることを表します。 arity-at-leastは、procが最低でも 引数(arity-at-least-value arity-at-least)を取ることを 表します。リストは、異なるアリティを持つ複数の手続きがあることを 表します。

Gaucheではいつでも、既存の手続きやジェネリック関数にメソッドを追加 できるので、arityが返す値はその手続きの現在の状態を示すに 過ぎません。その手続きやジェネリック関数に新しいメソッドが追加 されると、それも変更されます。

 
(arity cons) ⇒ 2
(arity list) ⇒ #<arity-at-least 0>
(arity make) ⇒ (#<arity-at-least 1>)
Function: arity-at-least? obj

objがarity-at-leastオブジェクトなら、真を返します。

Function: arity-at-least-value arity-at-least

arity-at-leastオブジェクトが表す必須引数の数を返します。

Function: procedure-arity-includes? proc k

手続きprocが引数kを取れる場合、#tを返します。 そうでなければ#fを返します。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.16.2 適用可能なオブジェクト

Gaucheでは、特別な組み込みの機構によって任意のオブジェクトを 「適用可能」にすることができます。

Generic Function: object-apply object arg

手続きでもジェネリックファンクションでもないオブジェクトが何らかの引数に 適用されたとき、そのオブジェクトと引数がジェネリックファンクションobject-apply に渡されます。

この機能は、具体的な例を挙げた方が説明し易いでしょう。

例えば、次のような式を評価しようとしたとします。

 
("abcde" 2)

オペレータは文字列に評価されますから、手続きでもジェネリックファンクションでも ありません。そこで、Gaucheはこの式を、あたかも次のような式が与えられた かのように解釈します。

 
(object-apply "abcde" 2)

デフォルトでは、<string><integer>を引数とする object-applyのメソッドは定義されていないので、 この式はエラーになります。しかし、次のようなメソッドを定義すると:

 
(define-method object-apply ((s <string>) (i <integer>))
  (string-ref s i))

最初の式はまるで文字列が整数に適用されたかのように動作します。

 
("abcde" 2) ⇒ #\c

このメカニズムは手続きが許されるほとんどの箇所で使うことができます。

 
(apply "abcde" '(1))   ⇒ (#\b)
(map "abcde" '(3 2 1)) ⇒ (#\d #\c #\b)

Gauche組み込みオブジェクトのうち、<regexp>オブジェクトと <regmatch>オブジェクトに対してはobject-applyメソッドが定義されて います。正規表現を参照して下さい。

Generic Function: (setter object-apply) object argvalue

適用可能オブジェクトを適用するフォームがset!フォームの第一ポジションに 現れた場合、そのフォームは下に示すように展開され、このメソッドが呼ばれます。

 
(set! (object arg …) value)
 ⇒ ((setter object-apply) object argvalue)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.16.3 継続

Function: call-with-current-continuation proc
Function: call/cc proc

[R5RS] 現在の継続を手続き (継続手続き) にパッケージ化して、それを引数として procを呼び出します。procが戻ったら、その返り値がcall/ccの 値となります。作成された継続手続きがどこかで0個または複数個の引数を伴って呼ばれたら、 あたかもcall/ccから戻ったかのように実行が継続されます。その場合、 call/ccは、継続手続きに与えられた引数を複数の値として返します。

ファーストクラスの継続はSchemeの最も特徴的な機能のひとつですが、それを 十分に説明するにはこの本の余白は狭すぎます。適切なドキュメントを参照してください。

Gaucheはわずかの例外を除いて、完全な継続をサポートしています。つまり継続は通常 無制限のエクステントを持ちます。しかし、継続がCコードからの「コールバック」 —SchemeがCで書かれたコードを呼び出し、それが再びSchemeコードを呼び出した場合— で作られたら、その継続のエクステントはコールバックが呼び出したCコードに戻るまでと なります。エクステントの切れた継続を呼ぼうとするとGaucheはエラーを報告します。 これは根本的な制限であり、おそらく解決されないでしょう。

なお、コールバックコードから有効な継続を呼ぶことは常に可能です。また、 高階関数を使うmapfor-eachapplyといった手続きは Cからのコールバックを使っておらず、この制限の影響を受けません。

おそらく、そのようなコールバック内で無制限のエクステントを持つ継続を作る必要というのは あまり無いでしょう。Gauche組み込みの機能では、以下のようなコードがCからのコールバックで 呼び出されます。さらに、外部のCライブラリを使った場合、例えばGUIツールキットからの コールバックなどはこの制限を受けるでしょう。

Macro: let/cc var body …

このマクロは次のように展開されます : (call/cc (lambda (var) body …)). APIはPLT Schemeから取りました。

Function: dynamic-wind before thunk after

[R5RS] beforethunkおよびafter は引数を取らない手続きです。 dynamic-windはまずbeforeを呼び出し、続いてthunkを呼び出し、 続いてafterを呼び出します。そしてthunkが返した値を返します。

もしdynamic-windのダイナミックスコープの外で捕捉された継続が thunkの中で呼ばれることにより制御がthunkから飛び出した場合、 (thunkの中でエラーが起こった場合などが考えられます)、 afterが呼ばれます。

もし、thunkの中で捕捉された継続がdynamic-windのダイナミックスコープの 外で呼ばれることにより制御がthunkの中へ飛び込んだ場合、 beforeが呼ばれます。

 
(letrec ((paths '())
         (c #f)
         (add (lambda (s) (push! paths s))))
  (dynamic-wind
   (lambda () (add 'connect))
   (lambda ()
     (add (call/cc (lambda (c0) (set! c c0) 'talk1))))
   (lambda () (add 'disconnect)))
  (if (< (length paths) 4)
      (c 'talk2)
      (reverse paths)))
 ⇒ (connect talk1 disconnect connect talk2 disconnect)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.16.4 多値

Function: values obj …

[R5RS] obj … を多値として返します。 呼び出し側は、組み込み構文の receive (変数束縛参照)か、 下に説明するR5RSの手続きcall-with-valuesを使って多値を受け取ることが できます。 srfi-11 - Let-valuesも参照してください。

 
(values 1 2) ⇒ 1 and 2
Function: call-with-values producer consumer

[R5RS] 手続きproducerを引数無しで呼びます。そして、それが返した値 を引数としてconsumerを呼びます。consumerが返す値を 返します。

 
(call-with-values (lambda () (values 1 2)) cons)
  ⇒ (1 . 2)
Macro: values-ref mv-expr k

mv-exprが返す多値のk-番目の値を返します。概念としては、 以下のコードと同じです。

 
(call-with-values (lambda () mv-expr) (lambda r (list-ref r k)))

このマクロは k がゼロであるような典型的な場合にはより単純な形へと 展開されます。

Common Lisp の nth-value に似ていますが、引数の順が逆になっています。 Scheme の他の*-ref 手続きと合わせるためです。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

6.16.5 遅延評価

GaucheではSRFI-45による拡張された遅延評価機構を用意しています。R5RSの ふたつのプリミティブではなく、lazydelayforce の3つのプリミティブがあります。

伝統的なdelayおよびforceだけを使ったのでは末尾再帰的なア ルゴリズムとの相性がよくないことがわかっています。末尾再帰的なアルゴリ ズムの本体が反復的に表現できるにもかかわらず、メモリを際限なく要求して しまいます。詳しい説明はSRFI-45のドキュメントを見てください。ここでは3 つのプリミティブの使い方を説明します。

Special Form: lazy expression
Special Form: delay expression

[SRFI-45][R5RS] これらの形式はexpressionの評価を遅延するプロミスを生成し ます。Expression はこのプロミスがforceにわたったときに評 価されます。

expression自身がプロミスを評価するようになっている場合には、 lazyを使うべきです。そうでなければ、delayを使うべきです。 型で考えればその違いは明白でしょう。

 
lazy  : Promise a -> Promise a
delay : a -> Promise a

Schemeでは静的な型付けをしないので、この使い分けを強制することができま せん。文脈にしたがってプログラマが適切に選択する必要があります。一般的 にはlazyは遅延アルゴリズムを表現している関数本体全体を囲む場合 にのみ出現します。

lazyの実用的な使用例についてはutil.stream (util.stream - ストリームライブラリ)の実装をチェックするといいでしょう。

Function: force promise

[R5RS] もし、promiseがプロミスでなければ、それをそのまま返します。

そうではない場合で、もしpromiseの値がまだ計算されていない場合には、 forcepromiseが内包している式を評価し、その結果を返します。

いったん、promiseの値が計算されると、その値はメモ化され、あとで 再びforceされても、再計算がおこなわれることはありません。

Function: promise? obj

objがプロミスオブジェクトである場合に #tを返します。

以下の例は遅延リストによるFibonacci数です。リストfibfib自身に1要素シフトしてfibを足し合せることで計算されま す。直前の要素をキャッシュとして使っているので、n番目のFibonacci数の計 算はO(n)ですみます。

 
(define (lcar lis)   ;; lazy car
  (car (force lis)))

(define (lcdr lis)   ;; lazy cdr
  (cdr (force lis)))

(define (ltake lis n)  ;; lazy take
  (if (<= n 0) '() (cons (lcar lis) (ltake (lcdr lis) (- n 1)))))

(define (lmap proc l1 l2)  ;; lazy map
  (if (null? l1)
    '()
    (cons (proc (lcar l1) (lcar l2))
          (delay (lmap proc (lcdr l1) (lcdr l2))))))

;; lazy list of fibonacci numbers
(define fibs (list* 1 1 (delay (lmap + fibs (cdr fibs)))))

;; take a look
(ltake fibs 20)
  ⇒ (1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 
      987 1597 2584 4181 6765)

これはエレガントな例ですが、n番目のFibonacci数を求めるのにO(n)の空間が 必要になるということに注意してください。これは、fibsの末尾にあ るdelay式がfibsリストの先頭を掴んだまま離さないからです。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]

This document was generated by Shiro Kawai on November, 22 2009 using texi2html 1.78.