closure
関数(λ式)を評価すると,システムは関数本体とそれを評価するための環境を合わせたclosure(クロージャ)を生成する.関数の環境は,それが評価されたときの環境であり,例えばある局所変数のスコープの元で関数が評価された(そしてクロージャが生成された)場合,その局所変数は,評価時に捨てられてしまうのではなく,関数呼び出しによって,いつでも参照できる.
==> (define x 0) ==> (define incx (let ((x 0)) (lambda () (set! x (+ x 1)) x))) incx ==> (incx) ;; let内の局所変数の値を増やす 1 ==> (incx) 2 ==> (incx) 3 ==> x ;;大域変数xの値 0
この場合,大域変数のxの値は変化していないことに注意.
==> (define new-counter (lambda (initial) (let ((x initial)) (lambda (method . a) (cond ((eq? method 'get) x) ((eq? method 'set) (set! x (car a))) ((eq? method 'add) (set! x (+ x (car a)))) ((eq? method 'sub) (set! x (- x (car a)))) ((eq? method 'reset) (set! x initial))))))) new-counter ==> (define c1 (new-counter 0)) ==> (define c2 (new-counter 10)) ==> (c1 'add 1) ==> (c1 'get) 1 ==> (c2 'sub 1) ==> (c2 'get) 9 ==> (c1 'reset) ==> (c1 'get) 0
c1,c2はそれぞれ独立して局所変数xを持っている.また局所変数xの対する操作は, 必ず引数methodで指定される内部の手続きを通して行われる. このように外部から独立したデータとそれを扱う手続きを持ち, 自律的に計算を行うものをオブジェクトと呼ぶ.
[kadai27opt]:選択問題(必ずしもやらなくてよい)ある人のデータとそのデータを操作する手続きを合わせて,以下のようなpersonというオブジェクトを作ることを考える.
- personのデータ:
- 名前(シンボル),年齢(整数),職業(シンボル),性別(シンボル),趣味(シンボルのリスト),友人(personのリスト)
- personの手続き:
- get-{name,age,occupation,sex,hobbies,friends} : それぞれののデータ
をそのまま返す.get-friendsにより,personという種類のオブジェクトのリス
トが返る.
- get-friend-names : 友人の名前のリストを返す(シンボルのリストである)
- set-{name,age,occupation,hobbies,friends} : それぞれのデータに新しい値を代入する
- add-hobbies,delete-hobbies(趣味の追加と削除),add-friends,delete-friends(友達を増やす,減らす)
- get-friend-names : 友人の名前のリストを返す(シンボルのリストである)
上の6つのデータをもらって新しいpersonを返す関数new-personを定義せよ.
;; 他にpersonが定義されていない場合,友人のリストの初期値は空リストになる. ==> (define honda (new-person 'taro '20 'student 'male '(tennis basket) '())) #<unspecified> ==> (honda 'get-name) taro ==> (honda 'set-age 21) 21 ==> (honda 'get-hobbies) (tennis basketball) ==> (honda 'add-hobbies 'programming 'cardgame) (tennis basketball programming cardgame) ==> (honda 'delete-hobbies 'tennis) (basketball programming cardgame) ==> (define williams (new-person 'ellen 19 'student 'female '() '())) #<unspecified> ==> (define jordan (new-person 'eddie 20 'student 'male '() '())) #<unspecified> ==> (honda 'add-friends williams jordan) #<unspecified> ==> (honda 'get-friend-names) (ellen eddie)[kadai28opt]:選択問題(必ずしもやらなくてよい)
personを引数にとって,友達の友達は皆友達というのを前提に,その全部の友人のつながりのリストを返す関数all-friendsを定義せよ.返すリストの中では,友人はその名前で表すものとする.ここで,次のような友達の関係があるとする.
Aの友達がB,C Bの友達がD Cの友達はなし Dの友達がF Fの友達はなし
このときAの友達のつながりは,次のように表される.
((A B) (A C) (B D) (D F))
基本的には,Aの友人のそれぞれについてその友人を調べ,またそれらの友人の友人を調べる,というように再帰的に友人を調べていけばよい.ただし友人関係に「BはAの友達,CはBの友達,AはCの友達」のようなループがある場合,単純な再帰呼び出しでは,無限に呼び出しが繰り返される.ここでは,上の例のように友人関係にはループがないものと仮定してよい.
余裕があれば,友人関係に「BはAの友達,CはBの友達,AはCの友達」のようなループがある場合に挑戦してみよ.
継続
[kadai29opt]:選択問題(必ずしもやらなくてよい)
kadai09(3)で定義した関数number-list?,
すなわち,与えられたリストが0個以上の数値要素だけからなるリストであれば真(#t),
そうでなければ偽(#f)を返す1引数関数を,継続を利用して大域脱出を行うように変更せよ.
- 与えられた任意個のリストをつなぎ合わせる関数,すなわちappendを作成してみよ. 関数名はmyappendとする.継続を利用する必要はない.
- 大域変数resume-withを用意して,myappendの実行後に,(resume-with x)を実行することで,myappendを実行した結果にxをappendして,残りの計算を続けることができるようにmyappendを変更した関数myappend-contを作成せよ.
;; 動作例 ==> (define resume-with '()) ==> (myappend-cont '(a b) '(c d) '(e f)) (a b c d e f) ==> (resume-with '(g h i)) (a b c d e f g h i) ==> (define resume-with1 resume-with) ==> (myappend-cont '(1 2 3 4 5) '(6 7)) (1 2 3 4 5 6 7) ==> (resume-with '(8)) (1 2 3 4 5 6 7 8) ==> (resume-with1 '(8)) (a b c d e f 8) ==> (apply + (myappend-cont '(1 2) '(3 4))) 10 ==> (resume-with '(8)) 18 ==> (resume-with '(8 9)) 27