Parenist養成ゼミ: シンボル、環境、関数
この記事は、Parenist養成ゼミシリーズの記事である。
今回は、シンボルがどのように束縛、評価されるのかを環境に着目して理解することを目的とした。また、使用例から関数の使用方法を学び、applyの仕組みが想像できるようにする。
recursionやspecial operator if
を解説していない関係上、課題の内容が不自然である。
マクローリン展開の項を増やすことによって、sin
に収束している様を確認できるのは受けが良かった。
シンボル
シンボルは任意の値への参照を一つ保持でき、評価されると保持している値を返す。シンボルに値を対応付ける行為を「シンボルを束縛する」などという。
シンボルと値の対応を記録しておくために、内部的には何らかの記憶域を専有する必要がある。この記憶域のことを環境という。
環境
環境は、零個以上のシンボルと値の対応と、零または一つの外側の環境への参照をもつ。
+-------+
|E1 |
+-------+
|a:1 |
|b:2 |
+-------+
| +-------+
|- |E2 |
| +-------+
| |b:20 |
| +-------+
| | +--------+
| |- |E3 |
| +--------+
| |a:100 |
| +--------+
| +--------+
|- |E4 |
| +--------+
| |a:1000 |
| |b:2000 |
| +--------+
...
上の図は、以下の状態を意味する。
環境E1
- 外側の環境への参照を持たない
- シンボルaを1に、bを2に束縛している。
環境E2
- 外側の環境への参照E1を持つ
- シンボルbを20に束縛している。
環境E3
- 外側の環境への参照E2を持つ
- シンボルaを100に束縛している。
環境E4
- 外側の環境への参照E1を持つ
- シンボルaを1000に、bを2000に束縛している。
環境E1のように、外側の環境への参照を持たない環境のことを大域環境といいシステム内にただ一つだけ存在する。
すべての環境は外側の環境をたどると大域環境に到達する。
束縛
シンボルを束縛するには、スペシャルオペレーター<-
を使用する。
) (<- pi 3.14)
3.14
) pi
3.14
束縛は以下の手順で行われる。
現在の環境にシンボルが既に束縛されている場合、その値を更新する。
そうでない場合は外側の環境を再帰的に辿る。
大域環境にもシンボルが束縛されていない場合は、大域環境に新たにシンボルを束縛する。
シンボルの評価は以下の手順で行われる。
現在の環境にシンボルが既に束縛されている場合、その値を返す。
そうでない場合、再帰的に外側の環境を辿る。
大域環境にも束縛されていない場合はエラーとなる。
環境の作成
スペシャルオペレータlet
は現在の環境を外側の環境に持つような新たな環境を作り、その環境下で評価を行う。
(let (var1 val1 var2 val2 ...)
expr1
expr2
...)
var -- 新たに作る環境に束縛するシンボル
val -- 束縛する値
expr -- 新たな環境下で評価する式
前述した図は以下のコードによってシミュレートすることができる。
(<- a 1 b 2)
(let (b 20)
; この環境下では、aは1に、bは20に解決される。
(let (a 100)
; この環境下では、aは100に、bは20に解決される。
))
; この環境下では、aは1に、bは2に解決される。
(let (a 1000 b 2000)
; この環境下では、aは1000に、bは2000に解決される。
)
; この環境下では、aは1に、bは2に解決される。
関数
関数は複数の式をまとめて抽象化する仕組みである。
(function function-name (args ...)
body ...)
function-name -- 定義する関数名
args -- 引数
body -- 実行する式
関数の変数という概念は、本質的には新たな環境を作成して、その中で評価を行うことと等しい。
例えば、以下の関数に対する呼び出しは次のような振る舞いに等しい。
(function sum3 (x y z)
(+ x y z))
(sum3 2 4 8)
(let (x 2 y 4 z 8)
(+ x y z))
課題
- 付録のファイル
fun.p
を作成せよ。 - 引数を2倍する関数
_*2
を実装せよ。 - 引数の階乗を返す関数
_!
を実装せよ。反復処理には付録のdotimesを使用すること。 - 2つの引数x, yを受け取り、xのy乗を返す関数
_^
を実装せよ。 - 引数xの正弦を返す関数
_sin
を実装せよ。ただし、マクローリン展開の最初の10項の和を返すものとする。 - 付録のファイルgen-sin-csv.pを作成せよ。
paren gen-sin-csv
を実行した結果をリダイレクトし、任意のグラフ描画アプリケーションに与えよ。- 引数xの正弦を返す関数
_sin2
を実装せよ。ただし、マクローリン展開の最初のn項の和を返すものとする。 gen-sin-csv.p
を参考に、0~10項まで展開した結果を同時に描画できるようなcsvファイルを作成するプログラムgen-sin2-csv.p
を作成せよ。数値から文字列に変換するにはstr
関数を、文字列を改行せずに出力するにはwrite-bytes
関数が使用できる。
実行例
$ paren fun.p
) (_*2 1)
2
) (_*2 2)
4
) (_*2 4)
8
) (_^ 1 0)
1
) (_^ 2 10)
1024
) (_! 1)
1
) (_! 2)
2
) (_! 3)
6
) (_! 4)
24
) (_! 5)
120
) (_sin 0)
0
) (_sin $pi/2)
1
) (_sin $pi)
-5.289187e-10
) (_sin $3pi/2)
-1.000003
) (_sin $2pi)
-0.001048
付録
fun.p
; symbol, environment, function
(<- $pi 3.14159265358979323846
$pi/2 (/ $pi 2)
$3pi/2 (* 3 $pi/2)
$2pi (* 2 $pi))
(function _*2 (x)
...)
(function _! (n)
...)
(function _^ (x y)
...)
(function _sin (x)
...)
(function _sin2 (x n)
...)
(function! main (args)
(repl))
dotimes
macro dotimesは、繰り返しを行うためのコンテキストを提供する。
dotimes自身そのものの返り値はnilであることに注意せよ。
) (dotimes (i 3)
(write i))
0
1
2
nil
) (let (sum 0)
(dotimes (i 3)
(write (list :i i :sum sum))
(<- sum (+ sum i)))
sum)
3
) (let (sum 0)
(dotimes (i 3)
(<- sum (+ sum i)))
0)
3
gen-sin-csv.p
; csv generator.
(load :fun)
(function! main (args)
(for (i 0) (< i $2pi) (i (+ i 0.1))
(write-line (str i "\t" (_sin i)))))