このサイトは学部では早稲田で物理を, 修士では東大で数学を専攻し, 今も非アカデミックの立場で数学や物理と向き合っている一市民の奮闘の記録です. 運営者情報および運営理念についてはこちらをご覧ください.
理系のための総合語学・リベラルアーツの視点から数学・物理・プログラミング・語学 (特に英語) の情報を発信しています. コンテンツアーカイブに見やすくまとめているのでぜひご覧ください.
目次
素数夜曲とこれまでの内容
素数夜曲
これまでの内容
- 素数夜曲とプログラミング B-B.1.2 Scheme, Python, Haskell, JavaScript 第 1 回
- 素数夜曲とプログラミング B.2-B.4 Scheme, Python, Haskell, JavaScript 第 2 回
- 素数夜曲とプログラミング B.4.3-B.4.7 Scheme, Python, Haskell, JavaScript 第 3 回
B.4.8 内部と外部
未定義の文字列 scope
を使って次のように入力するとクロージャになるらしい.
(lambda () scope)
そして括弧をつけて評価してみると未定義を警告するエラーが出る.
((lambda () scope))
RESULT
An error occurred.
いったん次のようにトップレベル (クロージャの外部) で定義する.
(define scope 'external)
(write ((lambda () scope)))
RESULT
external
これはクロージャの変数が scope
の外部定義された情報を参照していることを表す.
次にクロージャ内部に scope
を let
で定義してみる.
(write ((lambda ()
(let ((scope 'internal))
scope))))
RESULT
internal
もちろん内部で定義した scope
を使っている.
これを隠蔽 (shadow) と呼ぶそうだ.
変数の視野
クロージャ内部の let
を入れ子にしてさらに隠蔽の仕組みを調べる.
(write ((lambda ()
(let ((scope 'outer))
(let ((scope 'inner))
scope)))))
RESULT
inner
最後の scope
に近い内側の let
の定義が優先されている.
let
で lambda 項との束縛関係も定義できる.
(let ((times2 (lambda (x) (* x 2)))
(times3 (lambda (x) (* x 3))))
(write (times3 (times2 2))))
RESULT
12
これは define
による定義と似てはいるが times2
, times3
は大域的な関数として定義されない.
次の形式では束縛する引数がない.
(let ()
(define times2 (lambda (x) (* x 2)))
(define times3 (lambda (x) (* x 3)))
(write (times3 (times2 2)))
)
RESULT
12
let
の仕様を忘れたので次の確認コードを.
let
の外では times2
, times3
は未定義だ.
(let ()
(define times2 (lambda (x) (* x 2)))
(define times3 (lambda (x) (* x 3)))
)
(write (times3 (times2 2)))
RESULT
An error occurred.
これに対するのは begin
: begin
の外でも times2
と times3
は定義されている.
(begin (define times2 (lambda (x) (* x 2)))
(define times3 (lambda (x) (* x 3)))
(write (times3 (times2 2))))
(newline)
(write (times3 (times2 2)))
RESULT
12
12
let*
let
の複数の引数設定での順序の問題を考える.
例えば次のコードはエラーになる.
z
を設定するときに x
, y
が設定済みとは限らないから.
(let ((x 2) (y 3) (z (* x y)))
z)
RESULT
An error occurred.
let
を入れ子にする方法もあるが左から右へ順序を限定して設定する let*
もある.
まずは入れ子の let
を見てみよう.
(let ((x 2) (y 3))
(let ((z (* x y)))
(write z)))
RESULT
6
次に let*
.
(let* ((x 2) (y 3) (z (* x y)))
(write z))
RESULT
6
letrec
let
の使い分けシリーズ.
次のような形式でも使える.
(write ((letrec ((z (lambda () (* x y))) (x 2) (y 3))
z)))
RESULT
6
単独では未定義な (* x y)
を z
の値にするから,
単に (z (* x y))
とするとエラーになる (そういう実装もあるしエラーにならない実装もある).
letrec
は定数部分からも引数が見えるので再帰関数を定義するのに使える.
define
による定義に一番近い形式になるとのこと.
再帰関数の定義に関して名前からすればそうなのだろうが,
少なくとも B パートから読みはじめた限りでは本で実例が出てきていない.
こんな文章だけでは理解できないから P.443 から例を取ってくる.
まずは define
による形式から.
(define fact
(lambda (n)
(if (zero? n)
1
(* n (fact (- n 1))))))
(write (fact 4))
RESULT
24
次に letrec
.
(letrec ((fact
(lambda (n)
(if (zero? n)
1
(* n (fact (- n 1))) ))))
(write (fact 5)))
RESULT
120
書いてみたのはいいが letrec
でないといけないところはどこだろう.
letrec
を let
や let*
にするとエラーになることは確認した.
B.4.9 set!
既に定義されている変数の値を変更するためには set!
を使う.
!
は破壊的な変更を表すのに使う.
この流儀, 少なくとも Ruby は受け継いでいる.
他にも何かあった気がするがパッと思い浮かばない.
(set! [name] [new-value])
[name]
には値を変更する変数の名前を,
[new-value]
には新たな値を入れる.
試してみよう.
まずは同じ変数相手に define
を重ねるとエラーになる.
(define a 3)
(define a 4)
RESULT
An error occurred.
では set!
を.
(define a 3)
(write a)
(newline)
(set! a 4)
(write a)
RESULT
3
4
let
の内部でも同じ.
(let ((z 0))
(write z)
(newline)
(set! z 2)
(write z))
RESULT
0
2
大域変数
z
をトップレベルで定義すると大域変数になる.
Schem では大域変数をアスタリスクで囲む慣習があるようだ.
しかし短い変数名の変数についてはいちいち *z*
のようにはしないらしい.
何はともあれ次の評価を見てみよう.
(define z 5)
(write ((lambda ()
(let ((z 0))
(set! z (* 2 3))
(write z)
(newline))
z)))
RESULT
6
5
let
の内部で set!
をしていて, そこでの評価は確かに 6 になっている.
つまり破壊的に変更されている.
しかし let
の外にその変更の情報は出ていかないから lambda
の評価値は 5 になる.
このように内部定義に束縛されていない変数は外部に定義を探しに行く.
それはクロージャの窓口として機能する.
しかしこの「それはクロージャの窓口として機能する」というのが何を意味しているのかまだよくわかっていない.
とりあえず本にしたがってコードを書いていこう.
(define z 5)
((lambda ()
(set! z (* 3 5))))
(write z)
RESULT
15
確かに z
が破壊的に変更に変更されていることを示している.
(define z 5)
(write ((lambda ()
(set! z (* 3 5))
z)))
(newline)
(write z)
RESULT
15
15
こちらは lambda
が z
の値そのものを返している.
P.438 の冒頭は lambda
の中で lambda
の最後に書いた変数が
lambda
の返り値になっていると言っているだけで,
それ以上の何かを言っているわけではないと思うがどうなのだろう.
Ruby や Scala はそういう仕様だったと思う.
次のコードは Ruby のコード.
def mytest
a = 3
end
def mytest2
a = 4
a
end
p mytest
p mytest2
RESULT
3
4
特に関数 mytest2
を見てほしい.
最後に a
と書いただけで return a
と同じ意味になっている.
対比のために Python のコードを.
def mytest():
a = 3
a
def mytest2():
a = 4
return a
print(mytest())
print(mytest2())
RESULT
None
4
Python はきちんと return
しないと値が返らない.
コメントで何度もご指摘頂いたようにクロージャやスコープが全くわかっていないのだろう.
この辺の話すっきりしないものがずっと残っている.
しばらくやっているとそのうちわかるときが来るだろうから,
それまで耐えよう.
大域環境によるカウンタ
大域変数の値を set!
で変えると変更された値をまた大域環境から取り出せる.
(define n 0)
(write n)
(newline)
(set! n (+ n 1))
(write n)
(newline)
(set! n (+ n 1))
(write n)
(newline)
(set! n (+ n 1))
(write n)
(newline)
RESULT
0
1
2
3
次の count
関数を定義すると大域環境が壊れない限りカウントが続く.
(define count
(lambda ()
(set! n (+ n 1))
(write n)
(newline)))
(define n 0)
(count)
(count)
(count)
RESULT
1
2
3
Python, JavaScript, Haskell だとどう書くのだろう.
Python で次のようなコードを書いてみたがうまくいかない.
Python ならジェネレータで対応するような話なのだと勝手に思っているがよくわからない.
def count():
return n + 1
n = 0
print(count())
print(count())
print(count())
RESULT
1
1
1
次のようなコードが対応するコードだろうか.
def counter():
num = 0
while True:
yield num
num += 1
count = counter()
for i in count:
print(i)
if i >= 3:
break
print("ここで 4 が出る")
print(count.__next__())
for i in count:
print(i)
if i >= 7:
count.close()
RESULT
0
1
2
3
ここで 4 が出る
4
5
6
7
内部環境によるカウンタ
ここまで n
は無防備な状態で大域的にさらしていたので,
どこからどんな変更を受けるかわからず「安全」ではない.
そこで安全なカウンターとして扱うために変数を隠蔽したい.
ところで JavaScript で DOM の扱いが大変とかその辺の話とも絡むと思っている.
それはそれとして count
を改善しよう.
変数 n
を内部で定義すればいい.
(define count
((lambda (n)
(lambda () (set! n (+ n 1))))
0))
(write count)
(newline)
(write (count))
RESULT
#<procedure 10b86b1e0 at <current input>:77:5 ()>
#<unspecified>
素数夜曲には「(count)
を連打することで, くり返し評価値が返されて, 数値が 1 から順に上がっていく」とあるが,
どうやって n
を取り出せばいいのだろう.
いわゆるリターン的なやつはどこに仕込めばいいのか.
これ, lambda
が何なのか全くわかっていないからこの程度もわからないのだろう.
とてもつらい.
とりあえず本に沿って let
で書き直す.
(define count
(let ((n 0))
(lambda () (set! n (+ n 1)))))
RESULT
An error occurred.
何かエラーになった.
どこかタイポしているようだがどこだかわからない.
それはそれとして, 最初のコードに関して
力づくで次のような (私の当初の意図とは違う) 出力を得た.
(define count
((lambda (n)
(lambda () (set! n (+ n 1))
(write n)
(newline)))
0))
(count)
(count)
(count)
RESULT
1
2
3
あくまで count
では n
の値を返してほしい.
本によれば lambda
に先駆ける let
, 通称 let over lambda はクロージャを作り,
これがいい感じに振る舞ってくれるとかいう話.
クロージャどころか lambda
の理解さえあやふやになってきた.
追記
またもコメントを頂いた.
記事中のこれ
(define count
((lambda (n)
(lambda () (set! n (+ n 1))))
0))(define count
(let ((n 0))
(lambda () (set! n (+ n 1)))))は Scheme の手続きとしては誤っていないはずなのでただちにエラーになるものではありませんが、値を返さないのは set! フォームの返却値は未規定になっているからです。
(define count
(let ((n 0))
(lambda () (set! n (+ n 1))
n)))という風に最後に n を入れれば n の内容を返す手続きになります。
Scheme で言うところの「返却値が未規定」というのは、何を返すかは処理系の裁量で決めていいし、ユーザは何が返ってくるかあてにしてはいけないということです。 代入した値が返ってくるかもしれませんし、変数の名前が返ってくるものも存在しますし、状況に無関係なデタラメな値かもしれません。 Guile の場合は未規定を意味する型のオブジェクトが設けられていてそれが返るようですね。 #
と表示されるのはそういう意味です。
return
がないが,
やはり Scala や Ruby のように最後に値を入れれば return
的なことはしてくれるのか.
(define count
(let ((n 0))
(lambda () (set! n (+ n 1))
n)))
(write (count))
(newline)
(write (count))
RESULT
1
2
ここで最初 (count)
を count
と素の形で書いていて #<procedure 10a6234e0 at <current input>:149:4 ()>
と出力されてはまった.
(count)
としないといけないところを count
と書いてしまうのは,
根本的なところで Scheme を理解していないという気しかしない.
Emacs LISP を書かねばならないときも同じことをよく思う.
クォートをつけないといけないときもいまだによくわからない.
let
使わない方も書いておこう.
(define count
((lambda (n)
(lambda ()
(set! n (+ n 1))
n))
0))
(write (count))
(newline)
(write (count))
RESULT
1
2
ようやくできた.
generator
この手続きをさらに一般化するために次の generator
を作る.
(define generator
(lambda ()
(let ((n 0))
(lambda ()
(set! n (+ n 1))
(write n)
(newline)))))
(define count-1 (generator))
(define count-2 (generator))
(write count-1)
(newline)
(write count-2)
RESULT
#<procedure 10f6900a0 at <current input>:111:6 ()>
#<procedure 10f690080 at <current input>:111:6 ()>
値が返ってこない.
鬱陶しい.
(define generator
(lambda ()
((lambda (n)
(lambda () (set! n (+ n 1))
(write n)
(newline)))
0)))
(define count-1 (generator))
(define count-2 (generator))
(write (count-1))
(newline)
(write (count-1))
(newline)
(write (count-2))
(newline)
(write (count-1))
(newline)
RESULT
1
#<unspecified>
2
#<unspecified>
1
#<unspecified>
3
#<unspecified>
とりあえず独立したカウンターとして動作することを確認した.
しかし
(write (count-1))
あたりを削除するとエラーになる.
この程度もわかっていないとかさすがに震える.
カウンターの初期値を設定できる generator
を書いて終わり.
(define generator
(lambda (initial)
(let ((n initial))
(lambda ()
(set! n (+ n 1))
(write n)
(newline)))))
(define count-1 (generator -1))
(define count-2 (generator 0))
(write (count-1))
(newline)
(write (count-1))
(newline)
(write (count-1))
(newline)
(write (count-2))
(newline)
RESULT
0
#<unspecified>
1
#<unspecified>
2
#<unspecified>
1
#<unspecified>
これも多分上のコードと同じ変なところがあるはずだ.
Scheme はとても難しい印象を受けた.
追記
count
に関してコメントを頂いたことを受け,
書き直してみよう.
(define generator
(lambda (initial)
(let ((n initial))
(lambda ()
(set! n (+ n 1))
n))))
(define count-1 (generator -1))
(define count-2 (generator 0))
(write (count-1))
(newline)
(write (count-1))
(newline)
(write (count-1))
(newline)
(write (count-2))
(newline)
RESULT
0
1
2
1
中の lambda
の中で n
を返すことにしたらエラーにならなくなった.
それ自体はいいのだがまだピンと来ない.
正確に言うならもう一度同じような処理が必要になったとき,
混乱せずに一発できちんとしたコードを書ける自信がない.
じっくりやるしかない.
記事中のこれ
(define count
((lambda (n)
(lambda () (set! n (+ n 1))))
0))
(define count
(let ((n 0))
(lambda () (set! n (+ n 1)))))
は Scheme の手続きとしては誤っていないはずなのでただちにエラーになるものではありませんが、値を返さないのは
set!
フォームの返却値は未規定になっているからです。(define count
(let ((n 0))
(lambda () (set! n (+ n 1))
n)))
という風に最後に
n
を入れればn
の内容を返す手続きになります。Scheme で言うところの「返却値が未規定」というのは、何を返すかは処理系の裁量で決めていいし、ユーザは何が返ってくるかあてにしてはいけないということです。 代入した値が返ってくるかもしれませんし、変数の名前が返ってくるものも存在しますし、状況に無関係なデタラメな値かもしれません。 Guile の場合は未規定を意味する型のオブジェクトが設けられていてそれが返るようですね。
#<unspecified>
と表示されるのはそういう意味です。ありがとうございます。
動くコードを含めて追記しました。