続・SECDマシン
@t-sin
2021-03-25
lispmeetup #96
自己紹介 (t-sin)
●
Common Lispがお好きなWebプログラマ
●
興味のあること
– 言語処理系
– 音やグラフィクス
●
SNS
– GitHub: @t-sin
– Twitter: @sin_clav
発表の背景
●
lispmeetup #91でSECD機械の発表しました
– https://www.slideshare.net/t-sin/secd
●
上の発表にもでてくるLispKit Lisp本を読んでると
自分の理解が間違ってたことがわかったので
●
SECD機械の正しい説明をします
– 一部のみ誤りなので訂正を兼ねた復習です
●
前回答えられなかった質問も話します
– dumとrapの命令は何のためにあるか
もくじ
●
LispKit LispとSECD機械の概要
– SECD機械の関数の引数 (訂正ポイント)
●
dumとrap命令はなぜあるか (質問回答)
●
おまけ: LispKit Lisp本のおもしろかった章
– 手続き型プログラムを関数型のそれに変換する話
– 並列化を実現する拡張を入れる話
LispKit LispとSECD機械の概要
LispKit Lisp本について
●
Peter Henderson “Functional Programming: Application
and Implementation”
– 関数型プログラミングの利点や方法論の解説
– 関数型プログラミング言語の実装に関するトピック
– プログラム意味論や非決定性、並行性などのトピックもある
●
関数型プログラミングの説明では数学っぽい言語を使用
●
機械が処理するための言語としてS式のプログラムが登場
LispKit Lisp (1/3)
●
Peter Hendersonが著書内で定義したLisp処理系
●
純粋関数型言語で教育用なので代入や入出力はない
●
特殊形式16個と定数式と関数適用がある
●
この言語のターゲットアーキテクチャがSECD機械
LispKit Lisp (2/3)
●
特徴
– レキシカルスコープ
– 第一級の関数オブジェクト
●
高階関数が使える
– 関数の引数は1個のみ ← 前回発表の誤り
●
関数は固定長の引数を取れる
LispKit Lisp (3/3)
●
16個の特殊形式
– クォート: quote
– 算術演算: add, sub, mul, div, rem
– 比較演算: eq, lem
– コンスセル系: cons, car, cdr, atom
– フロー制御など: if, lambda, let, letrec
SECD機械
●
ラムダ計算を機械的に実行するための抽象機械
– Peter Landinが提唱
●
Landinの原論文のSECD機械はとても抽象的
●
機械語っぽい命令はLispKit Lispのものが有名
– 初なのかは不明
SECD機械の特徴
●
スタックマシン
– データはすべてスタックに積む
– 4本のスタックを持つ
●
スタック以外に状態を持たない
– 入出力もない
SECD機械のスタック
●
4つのスタック
– Stack: 命令や関数に渡すデータ置き場
– Environment: 引数と値のマッピング
– Control: 実行対象となる機械語列
– Dump: 関数適用や条件分岐時の復帰先を保持
●
スタックの実現にはコンスセルを用いる
– ので、SECD機械語もコンスセルで構成される
SECD機械の命令
●
数値演算: add, sub, mul, div, rem
●
比較演算: eq, leq
●
コンスセル系: cons, car, cdr
●
ロード系: ld, ldc, ldf
●
フロー制御: sel, join
●
関数適用: ap, rtn, dum, rap
dumとrap命令はなぜあるか
SECD機械での再帰 (1/2)
●
自分自身を再帰の間参照し続けられればよい
●
再帰する関数をスターター関数に渡せば
引数に束縛されっぱなしになることを利用
●
dumとrapは不要
SECD機械での再帰 (2/2)
●
こういうイメージ(Common Lisp)
これをSECD機械語でやるだけ
CL-USER> (funcall (lambda (f) (funcall f f 10))
(lambda (f n)
(if (= n 1)
1
(* n (funcall f f (- n 1))))))
3628800
SECD機械での相互再帰 (1/2)
●
再帰する関数の定義時に他の関数が定義されて
いる必要がある
●
単純な再帰で使ったテクニックは使えない
●
LispKit Lispではletrecを用いる
SECD機械での相互再帰 (2/2)
●
LispKit Lispのletrec
このletrecを実現するのにdumとrapが必要
(letrec (even? 10) ; 本体
(even? . (lambda (n) ; 本体だけで有効な定義1
(if (eq n 0)
t
(odd? (- n 1)))))
(odd? . (lambda (n) ; 本体だけで有効な定義2
(if (eq n 0)
Nil
(even? (- n 1))))))
letrec実現に必要な知識
●
SECD機械における関数オブジェクト
●
環境(Eスタック)の構造
●
通常の関数適用: ap命令
●
dumとrapのふるまい
SECD機械の関数オブジェクト
●
ldf命令を用いて定義する
●
ldf命令によって関数オブジェクトがSスタックトップに置かれる
– このときその時点のEスタックの内容 (環境) がコピーされる
●
関数オブジェクトは以下2つの要素で構成される
– 関数本体の機械語
– ldf実行時の環境
ldf (関数本体の機械語... rtn)
SECD機械の環境
●
関数の実引数リストのスタック
●
たとえばこんな感じ
●
参照するときは位置(n番目リストのm番目)で指定する
●
引数の名前から位置を求める計算はSECD機械語への
コンパイル時に済んでいると仮定する
;; 関数呼び出しが2段ネストしている状況
((10) (<関数1> 10))
通常の関数適用: ap命令 (1/2)
●
こんな動作をする:
– 関数と実引数リストをSスタックから取得
– 現在の実行コンテキスト(S, E, C)をDに退避
– Cスタックを関数の本体機械語に設定
– Eスタックを関数の環境に設定
– Eスタックトップに実引数リストをプッシュ
通常の関数適用: ap命令 (2/2)
●
各スタックは以下のように変化する
;; ap命令の実行前の状態
(S E C D) = ((本体コード . 関数の環境) (引数1 引数2))
(適用時点の環境)
(ap 適用後のコード)
(適用時点のダンプ))
;; ap命令の実行前の状態
(S E C D) = (()
((引数1 引数2) 関数の環境 …)
本体コード
((適用後のコード
適用時点の環境
適用時点のダンプ)))
dumとrapのふるまい: dum命令
●
現在の環境にダミー値をプッシュする
●
ダミー値はrapで置換するのでなんでもよい
相互再帰を実現するには
●
再帰する関数の定義時に他の関数が定義されてい
る必要がある
→再帰する関数の環境に他の関数が入っている 
必要がある
●
再帰する関数の環境にダミー値を入れておき
あとで他の関数の入ったリストで置き換える
 
dumとrapのふるまい: rap命令 (1/4)
●
だいたいap命令と同じ
– 関数と実引数リストをSスタックから取得
– 現在の実行コンテキスト(S, E, C)をDに退避
– Cスタックを関数の本体機械語に設定
– Eスタックを関数の環境に設定
– Eスタックのダミー値を実引数リストで置換
●
これでdumとrapの間で定義されたすべての関数の
環境に実引数リストが入る
dumとrapのふるまい: rap命令 (2/4)
●
rapの前後でのスタックの変化
;; ap命令の実行前の状態
(S E C D) = (((本体コード . (ダミー値 関数の環境))
(引数1 引数2))
(適用時点の環境)
(rap 適用後のコード)
(適用時点のダンプ))
;; ap命令の実行前の状態
(S E C D) = (()
((引数1 引数2) 関数の環境 …)
本体コード
((適用後のコード
適用時点の環境
適用時点のダンプ)))
dumとrapのふるまい: rap命令 (3/4)
●
注意すべき点
– letrecでは複数の値や関数が定義される
– ダミー値の置換はコンスセルのcar部を書き換えることで行われる
●
replca関数などで
– SECD機械の扱うオブジェクトはすべてコンスセルである
●
循環リストやコンスセルの共有も可能
●
dumの後に定義された関数のダミー値はrapですべて実引数リ
ストに置き換わる
dumとrapのふるまい: rap命令 (4/4)
●
letrecを実現するには
– dum命令を実行し
– 相互に再帰する関数を引数リストとして
– 最初の関数適用をキックする関数を用意し
– キックする関数をrapで呼ぶ
●
letrecできあがり!!!!!
LispKit Lisp本おまけ話
おもしろかった章の概要
手続き型→関数型変換 (1/2)
●
ALGOLのような手続き型言語の式・文を考える
●
LispKit Lispでインタプリタを書く
●
手続き型の各式・文の関数型言語での対応物を考える
– 式・文をLispKit Lispの関数に対応させる
●
状態から値への関数
– 表示的意味論というそうな
手続き型→関数型変換 (2/2)
●
フローチャートで表現されたプログラム
– ループや分岐などでグラフになる
●
フローチャートをループを含まない形にする
– ループを切って再帰にする
●
LispKit Lispの関数が得られる
並行性の導入
●
評価を遅延させられればコルーチンつくれる
– 中断・再開ができる関数
●
delay/forceを関数で実現
– “On Lisp”にもあるあの話
●
SECD機械全体を遅延評価するように改造
●
遅延評価の威力を体感 (未読)
– (コルーチンは……??)
まとめ
まとめ
●
SECD機械の上には純粋関数型言語を構成できる
●
dumとrap命令は相互再帰を実現するためにある
●
LispKit Lisp本にはほかにも以下のことが書かれている
– 手続き型プログラムを関数型のプログラムに変換できる
– 遅延評価を導入すると並行性を入れられる、らしい…?
追記
発表でもらったコメント等
dum/rapなくても相互再帰できる
●
発表の質疑応答時にいただいたコメントより
– 以下をSECD機械語に落としこむとできそう
–
–
–
–
– dam/rapはプリミティブではない…!
CL-USER> (funcall (lambda (c) (funcall (car c) c 10))
(cons (lambda (c n) ; even?
(if (zerop n)
t
(funcall (cdr c) c (- n 1))))
(lambda (c n) ; odd?
(if (zerop n)
nil
(funcall (car c) c (- n 1))))))
T
LispKit Lisp本
●
実は邦訳がある (懇親会にて)
– Peter Henderson著 杉藤芳雄, 二木厚吉 訳『関数型
プログラミング <コンピュータ・サイエンス研究書
シリーズ 17>』
並行性について
●
遅延評価を導入する方法以外にもいくつかある
(懇親会にて)
– LispKit Lisp本の非決定性の章にも関連する
●
“第11回 クロージャによる超軽量並行プロセスの簡単実
装法 | 日経クロステック(xTECH)”
https://xtech.nikkei.com/it/article/COLUMN/20070612/2742
31/

続・SECDマシン