lisp语言代替python_PLisp: 集成在Python中的LISP语言实现 (1)
["+", ["*", 3, 4], ['*', 5, 6]]
表示LISP的表達(dá)式:
(+ (* 3 4) (5 6))
又如,Python表達(dá)式:
["let",
[['x', 2],
['y', 3]],
['*', x, y]]
表示LISP的表達(dá)式:
(let
((x 2)
(y 3))
(* x y))
解釋的過程,就可以寫成一個Python函數(shù),輸入一個Python表達(dá)式,輸出另一個Python表達(dá)式。中間可以用副作用來處理IO。
如何實現(xiàn)呢?
首先,求值需要一個符號表,這個任何語言都一樣,記錄變量(或者LISP的符號)對應(yīng)的值。而LISP的符號表會隨著求值的過程(副作用)而改變。
class Context(UserDict):
def __init__(self, parent=None):
UserDict.__init__(self)
self.parent = parent
Context類表示一個符號表,或者說LISP求值的“上下文”。Context只不過是一個dict而已,輸入符號,對應(yīng)到值。這個parent稍候解釋,因為context可以嵌套。
然后,每個LISP函數(shù)都可以用Python函數(shù)表示。定義為由“上下文和參數(shù)的笛卡爾積”到python值的映射。簡而言之,每個LISP函數(shù)對應(yīng)的Python函數(shù),除了“普通參數(shù)”以外,還要Context作為額外的參數(shù)。比如:
def add(context, one, another):
return one + another
# ['+', 1, 2] calls this function and evaluates to 3
這個函數(shù)和符號表無關(guān),不讀取符號表,也不改變符號表。
又如:
def _set(context, key, value):
context[key] = value
return value
# ['set', 'x', 40] calls this function and sets symbol 'x' to 40
這是一個“賦值函數(shù)”,通過修改符號表,將符號key的對應(yīng)于value的值。
而
def _print(context, expr):
print expr
return expr
# ['print', ['quote', 'hello world!']] calls this function and prints "hello world!" to standard output
這個完全依賴求值的副作用,打印輸出。
函數(shù)的基本形式就是這樣。
基本的LISP執(zhí)行環(huán)境需要幾個基本的LISP函數(shù)。最基本的當(dāng)然是eval函數(shù)了。LISP中,eval的功能是將一個表達(dá)式求值。我定義Python函數(shù)_eval,為了避免和內(nèi)置函數(shù)eval命名沖突。
def _eval(context, expr): # 輸入expr,求expr的值
if isinstance(expr, str): # 對于符號,查找符號表獲得值
cur_context = context
# 因為符號表可以嵌套,所以迭代查找。
while cur_context is not None:
if expr in cur_context:
return cur_context[expr]
else:
cur_context = cur_context.parent
raise KeyError(expr)
elif isinstance(expr, list): # 對于表,轉(zhuǎn)換成函數(shù)調(diào)用。
first, rest = expr[0], expr[1:]
func = _eval(context, first) # 遞歸對第一個元素求值,得到函數(shù)。
if getattr(func,"call_by_name",False)==False: # 處理特殊函數(shù)。
evrest = [_eval(context, e) for e in rest]
else:
evrest = rest
return func(context, *evrest) # 調(diào)用函數(shù),得到返回值
else:
return expr # 對于其他原子,返回其本身。
正好分3個分支,處理了LISP求值的3種情況。
上述代碼提到了“特殊函數(shù)”。一般的函數(shù),調(diào)用前,要將參數(shù)求值,再傳入。如:
(+ (- 9 3) 4)
必須先求出(- 9 3)的值:6,才能傳入"+"函數(shù)。
但是,另外一些函數(shù)需要“按名傳遞”,即根據(jù)某些條件,選擇性地求值。如:
(if (eq (+ 1 1) 2) right wrong)
if函數(shù)是條件函數(shù)。先求第一個參數(shù)的值,如果是真,則求第二個參數(shù)的值,第三個參數(shù)不求值;如果是假,求第三個參數(shù)的值,第二個參數(shù)不動。
因此,對于這些特殊的LISP函數(shù),需要在Python函數(shù)上做一些標(biāo)記。我的做法是,在定義函數(shù)后,給這些函數(shù)設(shè)置其call_by_name的值為True。或者用@decorator更簡單。
典型的if函數(shù),定義如下:
@call_by_name
def _if(context, condition, iftrue, iffalse=None):
if _eval(context, condition):
return _eval(context, iftrue)
else:
return _eval(context, iffalse)
@call_by_name內(nèi)部完成call_by_name=True的賦值工作。而函數(shù)體內(nèi)部,先用_eval函數(shù)求第一個參數(shù)的值,對于真假兩種情況,分別調(diào)用兩個分支。
另一個典型的“按名傳遞”的函數(shù)是quote,這個函數(shù)避免其內(nèi)部的符號被求值。LISP中:
(quote abc)
'abc
兩者求值都得到符號abc。由于quote如此常用,因此LISP中有特殊的引號'語法,方便quote函數(shù)的應(yīng)用。
在Python中:
@call_by_name
def quote(context, expr):
return expr
def q(expr):
return ['quote',expr]
quote函數(shù)不對expr求值,而直接返回expr。我還定義了Python函數(shù)q,類似LISP的',方便書寫。
有了以上基本函數(shù),其實可以編一些簡單的程序了。以下是一個演示。
# 首先,使用之前,要實例化一個Context對象:
default_context = Context()
# 然后,將基本函數(shù)加入這個符號表
default_context["eval"] = _eval
default_context["print"] = _print
default_context["quote"] = quote
default_context["set"] = set
default_context["+"] = add
# 最后,用eval函數(shù)求值即可。
# 這是Hello world
_eval(default_context, ["print", q("Hello world!")])
# 屏幕上顯示Hello world!
# 賦值語句
_eval(default_context, ["set", q("x"), 5])
# 改變符號表,"x"對應(yīng)整數(shù)5。
# 計算簡單的加法
result = _eval(default_context, ["+", ["+", 1, "x"], 3])
print result
# 輸出9
總結(jié):
通過以上程序,可以看出,這種實現(xiàn),輸入是完全合法的Python表達(dá)式,輸出也是Python表達(dá)式。求值僅僅是用Python語言做了Python表達(dá)式的處理而已。
根據(jù)目前定義的函數(shù),這個“語言”功能還非常簡單;但是,下篇將引入更多函數(shù),使得這個“語言”逐漸趨近于功能完備的程序設(shè)計語言。
分享到:
2010-04-20 02:21
瀏覽 3485
評論
總結(jié)
以上是生活随笔為你收集整理的lisp语言代替python_PLisp: 集成在Python中的LISP语言实现 (1)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iphone11信号强度测试软件,信号差
- 下一篇: 使用Python转换PDF/Word/H