Posted 2020-07-23 17:31:13 GMT
bit誌1975-01月号 「連載LISP入門 (13) Backtrack法とLisp」にはbindq
というフォームが出てきます。
(1974年から1975年にかけてのbit誌での後藤英一先生のLisp連載)
HLISP独自のようですが、M式で書くと、
bindq[x;y;form]
のような形式でCommon Lispでいうprogv
に似た形式です。
q
はquote
のq
ですが、form
がクォートされるので、Common Lispで実装すると、
(defmacro bindq (vars vals form)
`(let (,@(mapcar #'list
(eval vars)
(eval vals)))
,form))
となります。
(let ((x 0))
(bindq (list 'x 'y)
(list 'x 42)
(progn (list x y))))
→ (0 42)
という動作ですが、クォートされた変数名がレキシカル変数を捕むという表記はCommon Lispの作法からすると気持ち悪いかもしれません……、と書いているうちに、HLISPはレキシカルスコープじゃないし、要するにprogv
ではないかとどんどん思えてきました。
この記事を書き始めたときには、とりあえずレキシカルスコープでprogv
的なもの、ということを考えていたのですが……、とりあえず、このまま続けることにします。
前述bindq
や、progv
ではフォームの束縛部のデータ型はリストでした。
let
でも((var val))
はリストですが、クォートされていて実行時に生成されるリストではありません。
上述で実装したbindq
は実行時に評価されそうな見た目ですが、マクロ展開時にフォームは固定されます。
マクロ展開時までに確定できれば変数でも大丈夫ですが、評価フェイズによってはエラーになったりするので、Common Lispの構文作法としてはあまり良くないでしょう。
(defvar *vars* '(x y))
(defvar *vals* '(0 42))(bindq *vars* *vals* (list x y))
→ (0 42)
まあでも一つの可能性としては面白いかもしれません。
リスト以外の束縛部のデータといえば、最近だとClojureが配列を採用していますが、古くは、Plasma(1974)があります。
Plasmaでは、sequence
という配列が[]
で表記され、set
という集合が{}
で表記されていますが、これらが、束縛部で使われます。
(let
{[x = 42] [y = 0]}
...)(labels
{[fib ≡
(cases
(≡> [0] 0)
(≡> [1] 1)
(≡> [=n]
(fib (n - 1) + (fib (n - 2)))))]}
...)
束縛部全体は集合で表記され、変数名と値の対は配列で表記されます。
面白いのが、束縛部を変数として与えることが可能なところで、
[math-definitions =
{[factorial ≡ ..]
[fibonacci ≡ ..]
[cosine ≡ ..]}]
(labels math-definitions body)
という記述が可能とされています。
上記では、labels
のスコープ内に導入しますが、大域環境に定義するenter
という機能もあります。
(enter math-definitions)
Plasmaはレキシカルスコープな筈ですが、この辺り実際レキシカルスコープで実現するのは難しそうな機能です。
実際どういう実装がされていたのかは謎……。
ちなみに、Common Lispで真似るならこんな感じでしょうか。
マクロ展開時までに束縛部のデータが確定していれば機能しますが、そうでない可能性を考えると脆弱な仕組みということが分かります。
(progn
(flet ((rdset (srm chr)
(let ((tab (make-hash-table :test #'equal)))
(dolist (elt (read-delimited-list #\} srm T) tab)
(if (and (typep elt '(vector T 3))
(member (elt elt 1) '(= ≡)))
(setf (gethash (elt elt 0) tab)
(elt elt 2))
(setf (gethash elt tab)
T))))))
(set-macro-character #\{ #'rdset))
(set-syntax-from-char #\} #\)) (flet ((rdseq (srm chr)
(coerce (read-delimited-list #\] srm T) 'vector)))
(set-macro-character #\[ #'rdseq))
(set-syntax-from-char #\] #\))
)
(defpackage plasma
(:use)
(:export let labels))
(defmacro plasma:let (binds &body body)
(let ((binds (eval binds)))
(check-type binds hash-table)
`(let ,(loop :for var :being :the :hash-keys :of binds :using (:hash-value val)
:collect `(,var ,val))
,@body)))
(plasma:let
{[x = 42] [y = 0]}
(list x y))
===> (let ((x 42) (y 0)) (list x y))
(defvar *binds* {[x = 42] [y = 0]})
(plasma:let *binds* (list x y))
→ (42 0)
束縛部を実行時データとして与えるというのは動的すぎるとしても、コンパイル時までに与えるというのは活用できる局面があったりするかもしれません。
実際の所、Common Lispではリード時までに与えるというのはたまにありますが、declare
等のコンパイラへの指示等が殆どで、束縛部を後で与えたいということは殆どないとは思いますが。
(defvar *bindspec* '((x 42) (y 0)))(let #.*bindspec* (list x y))
→ (42 0)
■
HTML generated by 3bmd in LispWorks Personal Edition 7.1.2