程序的構(gòu)建
當(dāng)你編寫的程序越來越大或者多人在同一代碼庫(kù)協(xié)作開發(fā)時(shí)闹获,將代碼劃分成不同的模塊是很有必要的. newLISP中使用context來創(chuàng)建模塊, 也就是命名空間. 命名空間將各個(gè)模塊之間的詞法隔離開. 這樣各模塊可以擁有相同名字的變量而不會(huì)發(fā)生沖突.
通常钓试,每個(gè)文件都有一個(gè)context來組織模塊.可能一個(gè)模塊文件包含數(shù)據(jù)庫(kù)訪問代碼.
? ? ; database.lsp
? ? ;
? ? (context 'db)
? ? (define (update x y z)
? ? ...
? ? )
? ? (define (erase x y z)
? ? ...
? ? )
另一個(gè)模塊包含多種數(shù)據(jù)操作代碼.
? ? ; auxiliary.lsp
? ? ;
? ? (context 'aux)
? ? (define (getval a b)
? ? ...
? ? )
最后猿涨,會(huì)有一個(gè)主模塊 MAIN 加載和控制所有其他模塊:
? ? ; application.lsp
? ? ;
? ? (load "auxiliary.lsp")
? ? (load "database.lsp")
? ? (define (run)
? ? ? ? (db:update ....)
? ? ? ? (aux:putval ...)
? ? ? ? ...
? ? ? ? ...
? ? )
? ? (run)
一個(gè)文件多個(gè)模塊
當(dāng)在一個(gè)文件中使用多個(gè)context時(shí)悼泌,每一個(gè)context都要以(context MAIN)來關(guān)閉:
? ? ; myapp.lsp
? ? ;
? ? (context 'A)
? ? (define (foo ...) ...)
? ? (context MAIN)
? ? (context 'B)
? ? (define (bar ...) ...)
? ? (context MAIN)
? ? (define (main-func)
? ? ? ? (A:foo ...)
? ? ? ? (B:bar ...)
? ? )
注意在創(chuàng)建context A 和 B 的時(shí)候,他們的名字前都有單引號(hào),這是因?yàn)樗麄兪切聞?chuàng)建的. 但是 MAIN 前沒有使用單引號(hào),這是因?yàn)?newLISP 在啟動(dòng)時(shí)就已經(jīng)創(chuàng)建了MAIN. 當(dāng)然, 給他加個(gè)引號(hào)也沒有問題.
可以使用下面的技術(shù)合并掉 (context MAIN)語句:
? ? ; myapp.lsp
? ? ;
? ? (context 'A)
? ? (define (foo ...) ...)
? ? (context 'MAIN:B)
? ? (define (bar ...) ...)
? ? (context 'MAIN)
? ? (define (main-func)
? ? ? ? (A:foo ...)
? ? ? ? (B:bar ...)
? ? )
(context 'MAIN:B) 回到 MAIN 并創(chuàng)建一個(gè)新的context B.
默認(rèn)函數(shù)
每一個(gè)模塊都可以有一個(gè)和宿主context同名的函數(shù). 這個(gè)函數(shù)有著特殊的作用:
? ? (context 'foo)
? ? (define (foo:foo a b c)
? ? ...
? ? )
函數(shù) foo:foo 就是模塊 foo的默認(rèn)函數(shù), 因?yàn)楫?dāng)使用模塊名foo 的時(shí)候就像調(diào)用一個(gè)函數(shù), 他將會(huì)被自動(dòng)的映射到 foo:foo
? ? (foo x y z)
? ? ; same as
? ? (foo:foo x y z)
默認(rèn)函數(shù)看起來像普通函數(shù),但是它擁有自己的名命名空間也榄。我們可以用此特性來編寫保持?jǐn)?shù)據(jù)狀態(tài)的函數(shù):
? ? (context 'generator)
? ? (define (generator:generator)
? ? ? ? (inc acc)) ; when acc is nil, assumes 0
? ? (context MAIN)
? ? (generator) → 1
? ? (generator) → 2
? ? (generator) → 3
下面是個(gè)更復(fù)雜的例子,用來生成斐波納契序列:
? ? (define (fibo:fibo)
? ? ? ? (if (not fibo:mem) (set 'fibo:mem '(0 1)))
? ? ? ? (last (push (+ (fibo:mem -1) (fibo:mem -2)) fibo:mem -1)))
? ? (fibo) → 1
? ? (fibo) → 2
? ? (fibo) → 3
? ? (fibo) → 5
? ? (fibo) → 8
? ? ...
這個(gè)示例還展示了如何在不顯示創(chuàng)建context的情況下動(dòng)態(tài)定義默認(rèn)函數(shù)。作為另一種選擇兵迅,也可以在明確創(chuàng)建context下定義默認(rèn)函數(shù):
? ? (context 'fibo)
? ? (define (fibo:fibo)
? ? ? ? ? ? (if (not mem) (set 'mem '(0 1)))
? ? ? ? ? ? (last (push (+ (mem -1) (mem -2)) mem -1)))
? ? (context MAIN)
? ? (fibo) → 1
? ? (fibo) → 2
? ? (fibo) → 3
? ? (fibo) → 5
? ? (fibo) → 8
雖然第一種形式較短,但是第二種形式更容易閱讀.
用context封裝數(shù)據(jù)
前面的例子展示了用命令空間中的默認(rèn)函數(shù)來封裝數(shù)據(jù). 在 generator 的例子中 acc 用來保存狀態(tài). 在 fibo 的例子中用 mem 來保存增長(zhǎng)的列表. 兩個(gè)例子里,函數(shù)和數(shù)據(jù)在一個(gè)命名空間中共存. 下面的例子展示了如何僅在命名空間的默認(rèn)函數(shù)中保存數(shù)據(jù):
? ? (set 'db:db '(a "b" (c d) 1 2 3 x y z))
就像使用默認(rèn)函數(shù) fibo 和 generator 那樣,我們可以用 db來調(diào)用 db:db . 此時(shí)db就像普通list一樣,可以完成任意的列表操作:
? ? (db 0)? ? → a
? ? (db 1)? ? → "b"
? ? (db 2 1)? → d
? ? (db -1)? → z
? ? (db -3)? → x
? ? (3 db)? ? → (1 2 3 x y z)
? ? (2 1 db)? → ((c d))
? ? (-6 2 db) → (1 2)
用引用傳遞對(duì)象
當(dāng)默認(rèn)函數(shù)被作為某個(gè)函數(shù)的參數(shù)調(diào)用的時(shí)候, 默認(rèn)函數(shù)會(huì)通過引用傳遞. 這意味這傳遞給函數(shù)的參數(shù)是原始數(shù)據(jù), 而不是列表或者字符串的拷貝. 在傳遞龐大的數(shù)據(jù)的時(shí)候這個(gè)特性非常有用:
? ? (define (update data idx expr)
? ? ? ? (if (not (or (lambda? expr) (primitive? expr)))
? ? ? ? ? ? (setf (data idx) expr)
? ? ? ? ? ? (setf (data idx) (expr $it))))
? ? (update db 0 99) → a
? ? db:db → (99 "b" (c d) 1 2 3 x y z)
? ? (update db 1 upper-case) → "b"
? ? db:db → (99 "B" (c d) 1 2 3 x y z)
? ? (update db 4 (fn (x) (mul 1.1 x))) →
? ? db:db → (99 "B" (c d) 1 2.2 3 x y z)
db:db 中的數(shù)據(jù)通過變量 data 傳遞給函數(shù) update , 現(xiàn)在update就擁有一個(gè) context db的數(shù)據(jù)引用. 接著判斷傳入的 expr 參數(shù)是否是系統(tǒng)函數(shù),操作符或者用戶定義的函數(shù)的,如果是的話就使用expr 操作 it , it是一個(gè)系統(tǒng)變量包含了表達(dá)式(這里是setf)要操作的原始數(shù)據(jù)(data idx).
當(dāng)一個(gè)newLISP函數(shù)需要一個(gè)列表或者在字符串參數(shù)的時(shí)候, 使用context的名稱就能將默認(rèn)函數(shù)傳遞進(jìn)去. 示例如下:
? ? (define (pop-last data)
? ? (pop data -1))
? ? (pop-last db) → z
? ? db:db? ? ? ? → (99 "B" (c d) 1 2.2 3 x y)
update 同時(shí)也展示了如何將函數(shù)作為數(shù)據(jù)傳遞給別的函數(shù) (upper-case 也依靠 $it). 更多內(nèi)容請(qǐng)看函數(shù)即數(shù)據(jù).