Lush/Lush入門
本章介紹了 Lush 的基本語法。有關類別的討論,請參見 類和物件。
像大多數解釋型語言一樣,Lush 命令可以在 Lush 提示符下互動式執行,也可以從文字檔案批次執行。
首先,在終端中輸入 "lush" 或 "lush.exe"(對於 Windows)啟動 Lush。
mkg@FRIDGE:~$ lush
這將列印版權宣告,然後將你帶入互動模式,並顯示 Lush 提示符。
LUSH Lisp Universal Shell (compiled on Jul 13 2006)
Copyright (C) 2002 Leon Bottou, Yann LeCun, AT&T, NECI.
Includes parts of TL3:
Copyright (C) 1987-1999 Leon Bottou and Neuristique.
Includes selected parts of SN3.2:
Copyright (C) 1991-2001 AT&T Corp.
This program is free software distributed under the terms
of the GNU Public Licence (GPL) with ABSOLUTELY NO WARRANTY.
Type `(helptool)' for details.
+[/usr/local/share/lush/sys/stdenv.dump]
[lushrc.lsh]
?
以下行將列印熟悉的第一個單詞。
$ (print "Hello world")
結果
"Hello world"
= "Hello world"
與它的母語 LISP 一樣,Lush 使用一對圓括號中以空格分隔的專案列表來呼叫命令。
(''function_name'' ''argument_1'' ... ''argument_n'')
此外,請注意,輸入 (print "Hello world") 不僅列印了 "Hello world",而且還返回了一個 "Hello world" 字串。你可以在上面的輸出中看到這一點,終端顯示輸入表示式的結果為 = "Hello world"。這引出了一個重要的一點:**所有 Lush 語句都有一個返回值。** 在 C/C++/Java 中最接近 "void 函式" 的是返回空列表 () 的函式。Lush 具有類似 C 的 printf 函式,它正是這樣做的。
$ (printf "I am %d today\n" 9)
結果
I am 9 today
= ()
Lush shell 中內建了一些可用性功能。
以下命令將開啟一個 GUI 介面,指向一個包含所有 Lush 相關內容的參考詞典。
$ (helptool)這是在查詢你確信存在的特定函式時首先要嘗試的操作。或者,如果你已經知道函式名稱,可以在 Lush 提示符下輸入以下內容。
$ ^A''function_name''
如果你不記得函式或變數的確切名稱,可以輸入其名稱的前幾個字母,然後按 Tab 鍵。如果存在無歧義的補全,Lush 將為你填入其餘字母。如果有多個符號符合條件,再次按 Tab 鍵將顯示可能的補全列表。例如,輸入 (prin 並按兩次 Tab 鍵將顯示以下內容。
$ (prin
prin printf print-window
print printrequester
按向上箭頭鍵將迴圈遍歷之前輸入的語句。
在 Lush 中,UNIX/Linux 專家熟悉的幾個常見的 shell 命令以函式的形式可用。例如
$ (ls)
=("pics" "todo.txt")
$ (cd "pics")
="/home/mkg/pics"
一些鍵盤快捷鍵對於 bash shell 或 emacs 的使用者來說是熟悉的。
- ctrl-a, ctrl-e: 轉到行首/行尾。
- ctrl-left, Ctrl-right: 跳過單詞。
一些函式有 ^_ 形式的快捷鍵。這些包括
- ^Afunction_name: 列印關於該函式的幫助資訊。
- ^Lsource_file.lsh : 載入並執行原始檔(參見下面的 "執行原始檔" 部分)。
請記住,不要在 ^_ 及其引數之間新增空格。
一系列 Lush 語句構成一個 Lush 程式,可以儲存在文字檔案中並以批處理模式執行:hello-world.lsh
; My first lush program
; Author: me
(print "Batch hello world")
要從 shell 中執行 hello-world.lsh,請鍵入
mkg@FRIDGE:~$ lush hello-world.lsh
或者,你可以在 Lush shell 中使用 libload 函式執行它。
$ (libload "hello-world.lsh")
".lsh" 字尾是可選的。或者,你可以使用 libload 的脫字元快捷鍵 ^L
$ ^Lhello-world.lsh
這些檔名也可以進行 Tab 補全。
Lush 直接從 LISP 繼承了其基於列表的語法。本節適用於沒有 LISP 經驗的人。
所有 LISP 程式都是一系列列表中的列表,程式透過從左到右 **評估** 每個列表來執行。這意味著每個列表中的第一個元素被視為一個函式,其餘元素被視為引數。評估完成後,列表將返回一個值(可能是 (),空列表)。
(setq semester-grade (+ (* 0.2 (grade-homework homework-1))
(* 0.2 (grade-homework homework-2))
(* 0.6 (grade-exam final-exam))))
在上面的示例中,最內層的列表(以 grade-... 函式開頭的列表)評估結果為家庭作業和考試成績。
(setq semester-grade (+ (* 0.2 87)
(* 0.2 95)
(* 0.6 93)))
它們的外層列表(以 * 開頭)評估結果為這些成績乘以某個係數。
(setq semester-grade (+ 17.4
19
55.8))
下一個外層列表(以 + 開頭)對這些乘積求和。
(setq semester-grade 92,2)
最外層的列表 `(setq ...)` 會將這個總和賦值給變數 "semester-grade"。它也返回一個值,在本例中是賦值變數的值。
92.2
建立列表
[edit | edit source]如上所示,Lisp 中的所有列表都會被求值。當您想建立一個本身是列表的變數時,這可能會不方便。以下是一個關於列表建立的簡單嘗試的示例
(defparameter possible-baby-names ("Grendel" "Greta" "Garbanzo"))
結果
*** "Grendel" : can't evaluate this list
Debug toplevel [y/N] ?問題在於,我們從未打算將字串 `“Grendel”` 視為函式,但 Lisp 並不瞭解這一點。有兩種解決方法。一種是使用引號運算子,在列表前加上單引號 `'`
(defparameter my-list '("Grendel" "Greta" "Garbanzo"))
這在這種情況下的效果很好,因為所有列表元素都是“字面量”,或者可以按原樣在剪下貼上方式下使用的表示式。但是,有時我們想將計算結果放入我們的列表中。在這種情況下,上述技術將產生以下不希望的結果
(defparameter my-list '(1 2 (+ 1 2)))
(print my-list)
結果
(1 2 (+ 1 2))
因此,引號運算子不僅阻止了第一個元素被用作函式,而且阻止了所有列表項被求值。因此,引號運算子主要用於移動未求值的程式碼片段,而不是將列表構建為變數(請參閱 Macros)。
我們真正想要的是使用 `(list` 列表建構函式
(defparamter my-list (list 1 2 (+ 1 2)))
(print my-list)
結果
(1 2 3)
概括地說,使用 `(list ...)` 來構造列表,並使用引號運算子來操作未求值的程式碼片段。您很可能大部分時間都會使用 `(list ...)`。
列表操作
[edit | edit source]討論 car、cdr、cons 和 append
更多
[edit | edit source]對於有興趣的人來說,在 Lisp tricks 部分還有更多內容。這些內容包括將函式像變數一樣傳遞、動態建立匿名函式(“lambda 函式”)以及指向有用的 Lisp 參考的連結。
變數
[edit | edit source]宣告全域性變數
[edit | edit source]可以使用 `defvar` 或 `defparameter` 命令來宣告和設定全域性變數
(defvar pi 3.14)
(defparameter time 20)
兩者完全相同,只有一個重要的細節:如果之前的 `defvar` 語句已經設定了某個符號的值,則對同一個符號名的第二次 `defvar` 不會做任何事情。另一方面,`defparameter` 將始終根據指示設定值
(defvar pi "three point one four") ; does nothing!
(defparameter time 100000) ; time set to 100000
<syntaxhighlight>
<code>defvar</code> should therefore be used with caution, especially when running lush programs from within the lush shell. If you run a program with a <code>defvar</code> statement, Edit the file to change the variable's value, then re-run the program, the variable will remain unchanged.
=== Declaring local variables===
The <code>let*</code> and <code>let</code> statements can be used to declare local variables. It consists of a list of <code>(''variable_name'' ''initial_value'')</code> pairs, followed by a body of code that may use the declared variables:
<syntaxhighlight lang="lisp">
(let* ((''name_1'' ''value_1'') [(''name_2'' ''value_2'') ... (''name_n'' ''value_n'')])
''body'' )
以下示例聲明瞭四個區域性變數,然後使用它們來製作咖哩
(let* ((potato-var (new potato))
(carrot-var (new carrot))
(onion-var (new onion))
(roux-var (new roux))
(peel potato-var)
(chop carrot-var)
(dice onion-var)
(make-curry potato-var carrot-var onion-var))let* 按它們列出的順序定義變數,而 `let` 不保證這種順序,允許可能的並行性。除非您確定這就是您想要的,否則您應該堅持使用 `let*`。`let*`/`let` 語句返回最後一個求值的表示式,這使得它們非常方便地設定帶有許多引數的函式呼叫,而不會使當前範圍混亂。以下示例建立了一個晚餐列表,包括牛奶、米飯和咖哩
(defparameter dinner (list "milk"
"rice"
(let* ((potato-var (new potato))
(carrot-var (new carrot))
(onion-var (new onion))
(roux-var (new roux))
(peel potato-var)
(chop carrot-var)
(dice onion-var)
(make-curry potato-var carrot-var onion-var))))
設定值
[edit | edit source]聲明後,可以使用 Lisp 中的 `setq` 更改變數值。以下展示了將數值變數 `theta` 設定為等於另一個變數 `pi`。
$ (setq theta pi)
如上所示,對數值應用 setq 會將第一個變數設定為第二個變數的相等但獨立的副本。與 Lisp 一樣,對於包含列表的變數也是如此。指向物件的變數則不同;變數本身就像 C/C++ 中的指標,賦值只是改變了指標的值。
布林值和條件語句
[edit | edit source]在 Lush 中,與 Lisp 一樣,空列表和非空列表代表布林值“真”和“假”。您也可以使用字面量 `t` 和 `nil`。與 Lisp 不同,Lush 還有許多除列表以外的物件,它們都求值為“真”。
數值比較
[edit | edit source]If 語句
[edit | edit source]When 語句
[edit | edit source]布林值陷阱
[edit | edit source]令人討厭的 `t`
[edit | edit source]Lush 從 Lisp 中繼承了一些歷史包袱,以“真”字面量 `t` 的形式出現。如此簡短的字面量名稱必然會被偶爾粗心的新手用作區域性變數,例如代表時間。雖然 Lush 會阻止使用者建立名為 `t` 的全域性變數,但名為 `t` 的區域性變數可能會導致微妙的和靜默的錯誤。
編譯後的 Lush 中的布林值
[edit | edit source]與 C/C++ 不同,整數 0 或指標值 `NULL` 不會在 Lush 條件語句中被視為“假”。為了使 Lush 條件語句理解整數或 C 布林表示式,必須使用 `to-bool` 函式
(when (to-bool #{ my_c_variable < 0 #} )
(print "my_c_variable is negative"))
迴圈
[edit | edit source]Lush 提供了傳統的 for 迴圈和 while 迴圈,以及用於遍歷陣列的更專門的函式。
For 迴圈
[edit | edit source]語法
(for (''counter'' ''start_value'' ''end_value'' [''step_size''])
''body'')
以下是如何使用 for 迴圈列印 0 到 10(包括 10)的數字的示例
? (for (i 0 10 1)
(printf "%d " i))
0 1 2 3 4 5 6 7 8 9 10 = ()
計數器 `i` 遍歷 0 到 10 之間的所有值,包括 10。迴圈表示式返回迴圈中求值的最後一個表示式。
可以使用可選的第三個引數指定步長
? (for (i 10 0 -1)
(printf "%d " i))
10 9 8 7 6 5 4 3 2 1 0 = ()
While 迴圈
[edit | edit source]語法
(while ''condition''
''body'')
While 迴圈對於遍歷列表很有用
$ (defparameter todo-list (list "Wake up" "Shower" "Leave home" "Return to home" "Put on clothes"))
$ (while todo-list
(print (car todo-list))
(setq todo-list (cdr todo-list)))
"Wake up"
"Shower"
"Leave home"
"Return home"
"Put on clothes"
= todo-list
與 for 迴圈一樣,while 迴圈返回body中求值的最後一個表示式。
大多數迭代將針對數值陣列(向量、矩陣或張量)的元素進行。雖然可以使用for 或 while 迴圈來實現,但以下迭代器函式提供了更方便的替代方案。
idx-bloop 透過遍歷陣列的第一維來遍歷陣列的連續子陣列。例如,矩陣的行可以按以下方式列印:
(defparameter mat [d[1 2 3][4 5 6]])
(idx-bloop ((row mat))
(print row))
輸出
[ 1.00 2.00 3.00 ]
[ 4.00 5.00 6.00 ]
可以同時迭代多個數組,只要它們的第一維包含相同數量的元素即可。
(defparameter mat (double-matrix 2 3))
(defparameter vec (double-matrix 2))
(idx-bloop ((row mat) (elem vec))
...)
idx-gloop 是 idx-bloop 的擴充套件,它提供了一個計數器變數,類似於 for 迴圈。以下是一個 idx-gloop 版本的列印行示例。
(defparameter mat [d[1 2 3][4 5 6]])
(idx-gloop ((row mat) (i))
(print "index:" i "row:" row))
</code>
Output:
<code>
"index:" 0 "row:" [ 1.00 2.00 3.00 ]
"index:" 1 "row:" [ 4.00 5.00 6.00 ]
idx-eloop 只是一個 idx-bloop,它遍歷最後一個索引而不是第一個索引。例如,當應用於矩陣時,idx-eloop 將遍歷其列,而不是其行。
- 與
for或while迴圈不同,陣列迴圈表示式返回陣列列表中的第一個陣列,而不是主體中評估的任何表示式。 - 子陣列是在堆上分配的,並且不應該期望它們在迴圈之外持續存在,即使您將它們分配給外部符號也是如此。這樣做會導致致命且神秘的記憶體錯誤。