newLISP 簡介/控制流程
有很多不同的方法可以控制程式碼的流程。如果你使用過其他指令碼語言,你可能會在這裡找到你最喜歡的,以及更多其他的方法。
所有控制流程函式都遵循 newLISP 的標準規則。每個函式的通用形式通常是一個列表,其中第一個元素是一個關鍵字,後面跟著一個或多個要計算的表示式。
(keyword expression1 expression2 expression3 ...)
也許你在任何語言中最簡單的控制結構就是簡單的if列表,它包含一個測試和一個操作。
(if button-pressed? (launch-missile))
第二個表示式,對launch-missile函式的呼叫,只有當符號button-pressed?計算為true時才會計算。1 為真。0 為真 - 畢竟它是一個數字。-1 為真。newLISP 已經知道的大部分東西都是真的。newLISP 知道有兩個重要的東西是假而不是真:nil和空列表 ()。任何 newLISP 不知道值的都是假的。
(if x 1)
; if x is true, return the value 1
(if 1 (launch-missile))
; missiles are launched, because 1 is true
(if 0 (launch-missile))
; missiles are launched, because 0 is true
(if nil (launch-missile))
;-> nil, there's no launch, because nil is false
(if '() (launch-missile))
;-> (), and the missiles aren't launched
你可以使用任何計算為真或假的東西作為測試。
(if (> 4 3) (launch-missile))
;-> it's true that 4 > 3, so the missiles are launched
(if (> 4 3) (println "4 is bigger than 3"))
"4 is bigger than 3"
如果一個符號計算為nil(可能是因為它不存在或沒有被分配一個值),newLISP 認為它是假的,並且測試返回nil(因為沒有提供備用操作)。
(if snark (launch-missile))
;-> nil ; that symbol has no value
(if boojum (launch-missile))
;-> nil ; can't find a value for that symbol
(if untrue (launch-missile))
;-> nil ; can't find a value for that symbol either
(if false (launch-missile))
;-> nil
; never heard of it, and it doesn't have a value
你可以新增第三個表示式,它是else操作。如果測試表達式計算為nil或(),則會計算第三個表示式,而不是第二個表示式,第二個表示式將被忽略。
(if x 1 2)
; if x is true, return 1, otherwise return 2
(if 1
(launch-missile)
(cancel-alert))
; missiles are launched
(if nil
(launch-missile)
(cancel-alert))
; alert is cancelled
(if false
(launch-missile)
(cancel-alert))
; alert is cancelled
這裡是一個典型的現實世界的三部分if函式,格式化是為了儘可能清晰地顯示結構。
(if (and socket (net-confirm-request)) ; test
(net-flush) ; action when true
(finish "could not connect")) ; action when false
雖然在測試之後有兩個表示式 - (net-flush)和(finish ...) - 但只有一個會被計算。
如果你沒有集中精力,可能會被其他語言中常見的指示詞(例如then和else)的缺失所迷惑!但你可以輕鬆地添加註釋。
你可以使用if來進行無限次的測試和操作。在這種情況下,if列表包含一系列測試-操作對。newLISP 會遍歷這些對,直到其中一個測試成功,然後計算該測試對應的操作。如果可以,請將列表格式化為列,以使結構更加明顯。
(if
(< x 0) (define a "impossible")
(< x 10) (define a "small")
(< x 20) (define a "medium")
(>= x 20) (define a "large")
)
如果你使用過其他 LISP 方言,你可能會認出這是一個與cond(條件函式)簡單的替代方案。newLISP 也提供了傳統的cond結構。請參閱選擇:if、cond 和 case。
你可能想知道如何在一個測試成功或不成功的情況下執行兩個或多個操作。有兩種方法可以做到這一點。你可以使用when,它就像一個沒有'else'部分的if。
(when (> x 0)
(define a "positive")
(define b "not zero")
(define c "not negative"))
另一種方法是定義一個表示式塊,這些表示式形成一個單獨的表示式,你可以將其用在if表示式中。我將在塊:表示式組中簡要討論如何做到這一點。
之前我提到過,當 newLISP 看到一個列表時,它會將第一個元素視為一個函式。我還應該提到它會先計算第一個元素,然後再將其應用於引數。
(define x 1)
((if (< x 5) + *) 3 4) ; which function to use, + or *?
7 ; it added
這裡,表示式的第一個元素(if (< x 5) + *),根據比較x與 5 的結果返回一個算術運算子。因此,整個表示式要麼是加法,要麼是乘法,具體取決於x的值。
(define x 10)
;-> 10
((if (< x 5) + *) 3 4)
12 ; it multiplied
此技術可以幫助你編寫簡潔的程式碼。而不是這個
(if (< x 5) (+ 3 4) (* 3 4))
你可以寫這個
((if (< x 5) + *) 3 4)
計算結果如下
((if (< x 5) + *) 3 4)
((if true + *) 3 4)
(+ 3 4)
7
注意每個表示式如何向外層函式返回一個值。在 newLISP 中,所有表示式都返回某個值,即使是if表示式。
(define x (if flag 1 -1)) ; x is either 1 or -1
(define result
(if
(< x 0) "impossible"
(< x 10) "small"
(< x 20) "medium"
"large"))
x的值取決於if表示式返回的值。現在符號result包含一個字串,具體取決於x的值。
有時你想多次重複一系列操作,進入迴圈。有多種可能性。你可能想對以下內容執行操作
- 列表中的每個專案
- 字串中的每個專案
- 特定次數
- 直到發生某些事情
- 在某些條件成立的情況下
newLISP 對所有這些問題都有解決方案,而且不止這些。
newLISP 程式設計師喜歡列表,因此dolist是一個非常有用的函式,它將一個區域性迴圈符號(變數)依次設定為列表中的每個專案,並在每個專案上執行一系列操作。在dolist之後,將要迴圈的變數名和要掃描的列表放在括號中,然後在後面加上操作。
在下面的示例中,我在定義一個區域性迴圈變數i之前,還設定了另一個名為counter的符號,它將儲存由sequence函式生成的數字列表的每個值。
(define counter 1)
(dolist (i (sequence -5 5))
(println "Element " counter ": " i)
(inc counter)) ; increment counter by 1
Element 1: -5 Element 2: -4 Element 3: -3 Element 4: -2 Element 5: -1 Element 6: 0 Element 7: 1 Element 8: 2 Element 9: 3 Element 10: 4 Element 11: 5
請注意,與if不同,dolist函式和許多其他控制詞允許你在一個列表中寫一系列表示式:這裡,println和inc(增量)函式都被呼叫了,用於列表中的每個元素。
有一個有用的快捷方式可以訪問系統維護的迴圈計數器。現在我使用了一個計數器符號,每次迴圈時都會增加它,以便跟蹤我們已經遍歷了列表的多少次。但是,newLISP 會自動為你維護一個迴圈計數器,它位於一個名為$idx的系統變數中,所以我無需使用計數器符號,只需每次迴圈時檢索$idx的值即可。
(dolist (i (sequence -5 5))
(println "Element " $idx ": " i))
Element 0: -5 Element 1: -4 Element 2: -3 Element 3: -2 Element 4: -1 Element 5: 0 Element 6: 1 Element 7: 2 Element 8: 3 Element 9: 4 Element 10: 5
在某些情況下,你可能更願意使用對映函式map來處理列表(稍後介紹 - 請參閱應用和對映:將函式應用於列表)。map可以用於將函式(現有函式或臨時定義)應用於列表中的每個元素,而無需使用區域性變數遍歷列表。例如,讓我們使用map來生成與上述dolist函式相同的輸出。我定義了一個臨時列印和增加函式,它包含兩個表示式,並將此函式應用於由sequence生成的列表中的每個元素。
(define counter 1)
(map (fn (i)
(println "Element " counter ": " i)
(inc counter))
(sequence -5 5))
經驗豐富的 LISP 程式設計師可能更熟悉lambda。fn是lambda的同義詞:使用你喜歡的任何一個。
你可能還會發現flat對遍歷列表很有用,因為它可以透過複製將包含巢狀列表的列表扁平化,以便更容易地處理。
((1 2 3) (4 5 6))
例如,
(1 2 3 4 5 6)
到
請參閱flat。
要遍歷傳遞給函式的引數,你可以使用doargs函式。請參閱引數:args。
遍歷字串(define alphabet "abcdefghijklmnopqrstuvwxyz")
(dostring (letter alphabet)
(print letter { }))
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
你可以使用等效於dolist的dostring來遍歷字串中的每個字元。
這些數字是 ASCII/Unicode 程式碼。
特定次數(dotimes (c 10)
(println c " times 3 is " (* c 3)))
0 times 3 is 0 1 times 3 is 3 2 times 3 is 6 3 times 3 is 9 4 times 3 is 12 5 times 3 is 15 6 times 3 is 18 7 times 3 is 21 8 times 3 is 24 9 times 3 is 27
如果你想執行特定次數的操作,請使用dotimes或for。dotimes會執行列表主體中指定的操作的特定次數。你應該提供一個區域性變數名,就像你在dolist中做的那樣。
你必須為這些形式提供一個區域性變數。即使你沒有使用它,你也要提供一個。
記住這一點的一個方法是想想生日。當你完成第一年時,你慶祝你的第一個生日,在這段時間裡你 0 歲。當你開始慶祝你的 10 歲生日時,你就完成了人生的前 10 年,這從你不再是 9 歲開始。newLISP 函式 first 獲取索引號為 0 的元素...
當你知道重複次數時使用 dotimes,但當你希望 newLISP 根據開始、結束和步長值來計算應該進行多少次重複時,使用 for。
(for (c 1 -1 .5)
(println c))
1 0.5 0 -0.5 -1
這裡 newLISP 足夠聰明,可以計算出我想以 0.5 的步長從 1 降到 -1。
再次提醒你 從 0 開始計數,比較以下兩個函式。
(for (x 1 10) (println x))
1 ... 10
(dotimes (x 10) (println x))
0 ... 9
for、dotimes 和 dolist 喜歡迴圈,反覆執行一組操作。通常重複會一直持續,直到達到極限——最後的數字或列表中的最後一個專案——為止。但是,你可以指定一個緊急逃生路線,形式為在下一個迴圈開始之前要執行的測試。如果該測試返回真值,則不會啟動下一個迴圈,表示式將在通常結束之前完成。這為你提供了一種在官方最終迭代之前停止的方法。
例如,假設你想將列表中的每個數字減半,但(出於某種原因)你想在其中一個數字為奇數時停止。比較此 dolist 表示式的第一個和第二個版本。
(define number-list '(100 300 500 701 900 1100 1300 1500))
; first version
(dolist (n number-list)
(println (/ n 2)))
50 150 250 350 450 550 650 750
; second version
(dolist (n number-list (!= (mod n 2) 0)) ; escape if true
(println (/ n 2)))
50 150 250
如果對 n 是否為奇數的測試 (!= (mod n 2) 0) 返回真值,則第二個版本停止迴圈。
請注意這裡使用了僅整數的除法。在示例中,我使用了 / 而不是浮點除法運算子 div。如果你想要另一個運算子,就不要使用一個!
你也可以為 for 和 dotimes 提供逃生路線測試。
對於更復雜的流程控制,你可以使用 catch 和 throw。throw 將表示式傳遞給之前的 catch 表示式,該表示式完成並返回表示式的值。
(catch
(for (i 0 9)
(if (= i 5) (throw (string "i was " i)))
(print i " ")))
輸出是
0 1 2 3 4
而 catch 表示式返回 i was 5。
你也可以使用布林函式設計流程。參見 塊:表示式組。
你可能有一個測試,用於測試一個情況,當發生有趣的事情時,該測試返回 nil 或 (),否則返回一個你並不感興趣的真值。若要重複執行一系列操作,直到測試失敗,請使用 until 或 do-until。
(until (disk-full?)
(println "Adding another file")
(add-file)
(inc counter))
(do-until (disk-full?)
(println "Adding another file")
(add-file)
(inc counter))
這兩者的區別在於測試何時執行。在 until 中,首先進行測試,然後如果測試失敗,則評估主體中的操作。在 do-until 中,首先評估主體中的操作,然後進行測試,最後進行測試以檢視是否可以進行另一個迴圈。
這兩個程式碼片段哪個是正確的?好吧,第一個在新增檔案之前測試磁碟的容量,但第二個使用 do-until,直到新增檔案後才檢查可用磁碟空間,這不太謹慎。
while 和 do-while 是 until 和 do-until 的互補相反,只要測試表達式保持為真,就會重複一個塊。
(while (disk-has-space)
(println "Adding another file")
(add-file)
(inc counter))
(do-while (disk-has-space)
(println "Adding another file")
(add-file)
(inc counter))
選擇每個的 do- 變體,以便在評估測試之前執行操作塊。
許多 newLISP 控制函式允許你構造一個操作塊:一組表示式,它們一個接一個地逐個進行評估。構造是隱式的:你不需要做任何事情,只需按照正確的順序,在正確的位置寫入它們即可。看看上面的 while 和 until 示例:每個示例都有三個表示式,它們將一個接一個地進行評估。
但是,你也可以使用 begin、or 和 and 函式顯式建立表示式塊。
當你想要在一個列表中顯式地將表示式組合在一起時,begin 很有用。每個表示式都會依次進行評估。
(begin
(switch-on)
(engage-thrusters)
(look-in-mirror)
(press-accelerator-pedal)
(release-brake)
; ...
)
等等。通常只有當 newLISP 期望單個表示式時才使用 begin。在 dotimes 或 dolist 結構中,你不需要它,因為這些結構已經允許使用多個表示式。
塊中每個表示式的結果無關緊要,除非它糟糕到足以完全停止程式。返回 nil 是可以的
(begin
(println "so far, so good")
(= 1 3) ; returns nil but I don't care
(println "not sure about that last result"))
so far, so good not sure about that last result!
塊中每個表示式返回的值在 begin 的情況下會被丟棄;只有最後一個表示式的值被返回,作為整個塊的值。但是對於另外兩個 block 函式 and 和 or,返回值很重要且有用。
and 函式會遍歷一個表示式塊,但如果其中一個表示式返回 nil(假),則會立即完成塊。若要到達 and 塊的末尾,每個表示式都必須返回一個真值。如果一個表示式失敗,則塊的評估停止,newLISP 會忽略剩餘的表示式,返回 nil,以便你知道它沒有正常完成。
以下是一個 and 的示例,它測試 disk-item 是否包含一個有用的目錄
(and
(directory? disk-item)
(!= disk-item ".")
(!= disk-item "..")
; I get here only if all tests succeeded
(println "it looks like a directory")
)
disk-item 必須透過所有三個測試:它必須是一個目錄,它不能是 . 目錄,也不能是 .. 目錄(Unix 術語)。當它成功透過這三個測試時,評估將繼續,並且列印訊息。如果一個測試失敗,則塊將在不列印訊息的情況下完成。
你也可以對數字表達式使用 and
(and
(< c 256)
(> c 32)
(!= c 48))
這測試 c 是否在 33 和 255(包括)之間,且不等於 48。這始終會返回真值或 nil,具體取決於 c 的值。
在某些情況下,and 可以產生比 if 更簡潔的程式碼。你可以將上一頁的示例改寫為
(if (number? x)
(begin
(println x " is a number ")
(inc x)))
以使用 and 代替
(and
(number? x)
(println x " is a number ")
(inc x))
你也可以在這裡使用 when
(when (number? x)
(println x " is a number ")
(inc x))
or 函式比它的對應函式 and 更容易滿足。表示式序列將逐個進行評估,直到一個表示式返回一個真值。然後忽略其餘的表示式。你可以使用它來遍歷一系列重要條件,任何一個失敗都足以放棄整個操作。或者,相反地,使用 or 來遍歷一個列表,其中任何一個成功都是繼續進行的理由。無論如何,請記住,一旦 newLISP 獲得一個非 nil 結果,or 函式就會完成。
以下程式碼設定了一系列條件,每個數字都必須避免滿足這些條件——只要一個答案為真,它就不會被打印出來
(for (x -100 100)
(or
(< x 1) ; x mustn't be less than 1
(> x 50) ; or greater than 50
(> (mod x 3) 0) ; or leave a remainder when divided by 3
(> (mod x 2) 0) ; or when divided by 2
(> (mod x 7) 0) ; or when divided by 7
(println x)))
42 ; the ultimate and only answer
你可能不會找到 amb 的好用途——歧義函式。給定列表中的一系列表示式,amb 將選擇並評估其中一個表示式,但你事先不知道哪個表示式會被選擇
> (amb 1 2 3 4 5 6)
3
> (amb 1 2 3 4 5 6)
2
> (amb 1 2 3 4 5 6)
6
> (amb 1 2 3 4 5 6)
3
> (amb 1 2 3 4 5 6)
5
> (amb 1 2 3 4 5 6)
3
>...
使用它隨機選擇替代操作
(dotimes (x 20)
(amb
(println "Will it be me?")
(println "It could be me!")
(println "Or it might be me...")))
Will it be me? It could be me! It could be me! Will it be me? It could be me! Will it be me? Or it might be me... It could be me! Will it be me? Will it be me? It could be me! It could be me! Will it be me? Or it might be me... It could be me! ...
若要測試一系列替代值,可以使用 if、cond 或 case。case 函式允許你根據切換表示式的值執行表示式。它由一系列值/表示式對組成
(case n
(1 (println "un"))
(2 (println "deux"))
(3 (println "trois"))
(4 (println "quatre"))
(true (println "je ne sais quoi")))
newLISP 依次遍歷這些對,檢視 n 是否與 1、2、3 或 4 中的任何一個值匹配。一旦找到一個匹配的值,就會評估表示式,case 函式就會完成,並返回表示式的值。最好將最後一對與 true 和一個通配表示式一起使用,以應對完全沒有匹配的情況。如果 n 是一個數字,那麼它總是為真,所以將其放在最後。
潛在的匹配值不會被評估。這意味著你不能寫成這樣
(case n
((- 2 1) (println "un"))
((+ 2 0) (println "deux"))
((- 6 3) (println "trois"))
((/ 16 4) (println "quatre"))
(true (println "je ne sais quoi")))
儘管從邏輯上講這應該有效:如果 n 為 1,你希望第一個表示式 (- 2 1) 匹配。但該表示式並未被評估——所有求和都未被評估。在本例中,true 操作 (println "je ne sais quoi") 被評估。
如果你想使用一個評估其引數的 case 版本,在 newLISP 中很容易做到。參見 宏。
之前我提到過 cond 是 if 的更傳統的版本。newLISP 中的 cond 語句具有以下結構
(cond
(test action1 action2 etc)
(test action1 action2 etc)
(test action1 action2 etc)
; ...
)
每個列表都包含一個測試,後面跟著一個或多個表示式或動作,如果測試返回 true,則評估這些表示式或動作。newLISP 會執行第一個測試,如果測試為真,則執行動作,然後忽略其餘的測試/動作迴圈。測試通常是一個列表或列表表示式,但它也可以是一個符號或一個值。
一個典型的例子如下所示
(cond
((< x 0) (define a "impossible") )
((< x 10) (define a "small") )
((< x 20) (define a "medium") )
((>= x 20) (define a "large") )
)
這與 if 版本基本相同,只是每一對測試-動作都用括號括起來。以下是 if 版本,供比較
(if
(< x 0) (define a "impossible")
(< x 10) (define a "small")
(< x 20) (define a "medium")
(>= x 20) (define a "large")
)
對於更簡單的函式,使用 if 可能更容易。但是 cond 在編寫較長的程式時可讀性更高。如果您希望特定測試的動作評估多個表示式,cond 的額外括號可以使您的程式碼更短
(cond
((< x 0) (define a -1) ; if would need a begin here
(println a) ; but cond doesn't
(define b -1))
((< x 10) (define a 5))
; ...
控制函式 dolist、dotimes 和 for 涉及臨時區域性符號的定義,這些符號在表示式持續期間存在,然後消失。
類似地,使用 let 和 letn 函式,您可以定義僅在列表內部存在的變數。它們在列表之外無效,並且在列表完成評估後會失去其值。
let 列表中的第一個專案是一個子列表,其中包含變數(不需要加引號)和表示式來初始化每個變數。列表中的其餘專案是可以訪問這些變數的表示式。最好將變數/起始值對對齊
(let
(x (* 2 2)
y (* 3 3)
z (* 4 4))
; end of initialization
(println x)
(println y)
(println z))
4 9 16
此示例建立三個區域性變數 x、y 和 z,併為每個變數分配值。主體包含三個 println 表示式。這些表示式完成後,x、y 和 z 的值將不再可用 - 儘管整個表示式返回最終 println 語句返回的值 16。
如果您將 let 列表想象成一行,則其結構很容易記住
(let ((x 1) (y 2)) (+ x y))
如果您想在第一個初始化部分的其他地方引用區域性變數,請使用 letn 而不是 let
(letn
(x 2
y (pow x 3)
z (pow x 4))
(println x)
(println y)
(println z))
在 y 的定義中,您可以引用 x 的值,我們剛剛將其定義為 2。letn 是 let 的巢狀版本,它允許您執行此操作。
我們對區域性變數的討論引出了函式。
define 函式提供了一種方法,可以在名稱下儲存表示式列表,以便稍後執行。您定義的函式可以使用與 newLISP 的內建函式相同的方式。函式定義的基本結構如下所示
(define (func1)
(expression-1)
(expression-2)
; ...
(expression-n)
)
當您不想向函式提供任何資訊時,或者像這樣,當您需要提供資訊時
(define (func2 v1 v2 v3)
(expression-1)
(expression-2)
; ...
(expression-n)
)
您像呼叫任何其他函式一樣呼叫新定義的函式,如果您的定義需要,則在列表中傳遞值
(func1) ; no values expected
(func2 a b c) ; 3 values expected
我說預期,但 newLISP 很靈活。您可以向 func1 提供任意數量的引數,newLISP 不會抱怨。您也可以使用任意數量的引數呼叫 func2 - 在這種情況下,如果引數不足以定義它們,則 a、b 和 c 在開始時將設定為 nil。
函式執行時,主體中的每個表示式都按順序進行評估。最後評估的表示式的值將作為函式的值返回。例如,此函式根據 n 的值返回 true 或 nil
(define (is-3? n)
(= n 3))
> (println (is-3? 2)) nil > (println (is-3? 3)) true
有時您希望顯式指定要返回的值,方法是在末尾新增一個表示式,該表示式計算為正確的值
(define (answerphone)
(pick-up-phone)
(say-message)
(set 'message (record-message))
(put-down-phone)
message)
末尾的 message 計算為 (record-message) 收到並返回的訊息,(answerphone) 函式返回此值。如果沒有它,函式將返回 (put-down-phone) 返回的值,該值可能只是一個真值或假值。
要使函式返回多個值,您可以返回一個列表。
在函式的引數列表中定義的符號是函式的區域性符號,即使它們之前在函式之外存在
(set 'v1 999)
(define (test v1 v2)
(println "v1 is " v1)
(println "v2 is " v2)
(println "end of function"))
(test 1 2)
v1 is 1 v2 is 2 end of function > v1 999
如果符號在函式主體中定義為這樣
(define (test v1)
(set 'x v1)
(println x))
(test 1)
1 > x 1
它也可以從函式外部訪問。這就是為什麼您需要定義區域性變數的原因!請參閱 區域性變數。
newLISP 足夠聰明,不會擔心您是否提供超過所需的資訊
(define (test)
(println "hi there"))
(test 1 2 3 4) ; 1 2 3 4 are ignored
hi there
但它不會為您填補空白
(define (test n)
(println n))
>(test) ; no n supplied, so print nil nil > (test 1) 1 > (test 1 2 3) ; 2 and 3 ignored< 1
有時您希望函式更改程式碼其他地方符號的值,有時您希望函式不更改或不能更改。以下函式在執行時會更改 x 符號的值,該符號可能在程式碼的其他地方定義,也可能沒有定義
(define (changes-symbol)
(set 'x 15)
(println "x is " x))
(set 'x 10)
;-> x is 10
(changes-symbol)
x is 15
如果您不希望發生這種情況,請使用 let 或 letn 定義一個區域性 x,它不會影響函式外部的 x 符號
(define (does-not-change-x)
(let (x 15)
(println "my x is " x)))
(set 'x 10)
> (does-not-change-x) my x is 15 > x 10
x 在函式外部仍然是 10。函式內部的 x 與函式外部的 x 不同。當您使用 set 更改函式內部區域性 x 的值時,它不會更改任何外部的 x
(define (does-not-change-x)
(let (x 15) ; this x is inside the 'let' form
(set 'x 20)))
>(set 'x 10) ; this x is outside the function 10 > x 10 > (does-not-change-x) > x 10
您可以使用 local 函式代替 let 和 letn。這與 let 和 letn 相似,但您不必在第一次提及區域性變數時為它們提供任何值。它們只是 nil,直到您設定它們為止
(define (test)
(local (a b c)
(println a " " b " " c)
(set 'a 1 'b 2 'c 3)
(println a " " b " " c)))
(test)
nil nil nil 1 2 3
還有其他方法可以宣告區域性變數。當您定義自己的函式時,您可能會發現以下技術更容易編寫。注意逗號
(define (my-function x y , a b c)
; x y a b and c are local variables inside this function
; and 'shadow' any value they might have had before
; entering the functions
逗號是一個巧妙的技巧:它是一個普通的符號名稱,如 c 或 x
(set ', "this is a string")
(println ,)
"this is a string"
- 它不太可能用作符號名稱,因此它在引數列表中用作視覺分隔符非常有用。
在函式定義中,您在函式的引數列表中定義的區域性變數可以分配預設值,如果在呼叫函式時您沒有指定值,則將使用這些預設值。例如,這是一個具有三個命名引數 a、b 和 c 的函式
(define (foo (a 1) b (c 2))
(println a " " b " " c))
如果您在函式呼叫中沒有提供值,則符號 a 和 c 將分別取值 1 和 2,但 b 將為 nil,除非您為它提供值。
> (foo) ; there are defaults for a and c but not b
1 nil 2
(foo 2) ; no values for b or c; c has default
2 nil 2
> (foo 2 3) ; b has a value, c uses default
2 3 2
> (foo 3 2 1) ; default values not needed
3 2 1
>
您可以看到 newLISP 對函式的引數非常靈活。您可以編寫接受任意數量引數的定義,從而為您(或您的函式的呼叫者)提供最大的靈活性。
args 函式返回傳遞給函式的任何未使用的引數
(define (test v1)
(println "the arguments were " v1 " and " (args)))
(test)
the arguments were nil and ()
(test 1)
the arguments were 1 and ()
(test 1 2 3)
the arguments were 1 and (2 3)
(test 1 2 3 4 5)
the arguments were 1 and (2 3 4 5)
請注意,v1 包含傳遞給函式的第一個引數,但任何剩餘的未使用的引數都包含在 (args) 返回的列表中。
使用 args,您可以編寫接受不同型別輸入的函式。注意以下函式如何可以不帶引數呼叫,也可以帶字串引數、數字或列表呼叫
(define (flexible)
(println " arguments are " (args))
(dolist (a (args))
(println " -> argument " $idx " is " a)))
(flexible)
arguments are ()
(flexible "OK")
arguments are ("OK")
-> argument 0 is OK
(flexible 1 2 3)
arguments are (1 2 3)
-> argument 0 is 1
-> argument 1 is 2
-> argument 2 is 3
(flexible '(flexible 1 2 "buckle my shoe"))
arguments are ((flexible 1 2 "buckle my shoe"))
-> argument 0 is (flexible 1 2 "buckle my shoe")
args 允許您編寫接受任意數量引數的函式。例如,newLISP 非常樂意讓您將一百萬個引數傳遞給一個定義適當的函式。我嘗試過
(define (sum)
(apply + (args)))
(sum 0 1 2 3 4 5 6 7 8
; ...
999997 999998 999999 1000000)
; all the numbers were there but they've been omitted here
; for obvious reasons...
;-> 500000500000
實際上,newLISP 對此很滿意,但我的文字編輯器不滿意。
doargs 函式可以代替 dolist 使用,以遍歷 args 返回的引數。您可以將 flexible 函式編寫為
(define (flexible)
(println " arguments are " (args))
(doargs (a) ; instead of dolist
(println " -> argument " $idx " is " a)))
newLISP 還有更多方法可以控制程式碼執行流程。除了 catch 和 throw 之外,它們允許您處理和捕獲錯誤和異常,還有 silent,它就像 begin 的靜音版本一樣工作。
如果您需要更多功能,可以使用 newLISP 宏編寫自己的語言關鍵字,這些關鍵字可以像使用內建函式一樣使用。請參閱 宏。
考慮以下函式
(define (show)
(println "x is " x))
請注意,此函式引用了一些未指定的符號 x。這在定義或呼叫函式時可能存在也可能不存在,它可能具有值也可能沒有值。當評估此函式時,newLISP 會尋找最接近的名為 x 的符號,並找到其值
(define (show)
(println "x is " x))
(show)
x is nil
(set 'x "a string")
(show)
x is a string
(for (x 1 5)
(dolist (x '(sin cos tan))
(show))
(show))
x is sin x is cos x is tan x is 1 x is sin x is cos x is tan x is 2 x is sin x is cos x is tan x is 3 x is sin x is cos x is tan x is 4 x is sin x is cos x is tan x is 5
(show)
x is a string
(define (func x)
(show))
(func 3)
x is 3
(func "hi there")
x is hi there
(show)
x is a string
您可以看到 newLISP 如何透過動態跟蹤哪個 x 是活動的來始終為您提供 當前 x 的值,即使可能在後臺潛伏著其他 x。只要 for 迴圈開始,迴圈變數 x 就會接管作為 當前 x,但該 x 會立即被列表迭代變數 x 取代,該變數取幾個三角函式的值。在每組三角函式之間,迴圈變數版本的 x 會短暫地再次彈出。在所有這些迭代之後,字串值再次可用。
在 func 函式中,還有另一個 x,它對函式是區域性的。當呼叫 show 時,它將列印這個區域性符號。對 show 的最後一次呼叫將返回 x 所具有的第一個值。
雖然 newLISP 不會因為所有這些不同的 x 而感到困惑,但您可能會感到困惑!因此,最好使用更長且更具說明性的符號名稱,並使用區域性變數而不是全域性變數。如果您這樣做,您犯錯或在以後誤讀程式碼的可能性較小。一般來說,不要在函式中引用未定義的符號,除非您確切地知道它來自哪裡以及它的值是如何確定的。
這種動態跟蹤符號的當前版本的流程稱為動態作用域。在您檢視上下文 (Contexts) 時,將對此主題進行更多介紹。它們提供了一種組織類似命名符號的替代方法 - 詞法作用域。