跳轉到內容

Lush/Lush入門

來自 Wikibooks,開放世界的開放書籍

本章介紹了 Lush 的基本語法。有關類別的討論,請參見 類和物件

像大多數解釋型語言一樣,Lush 命令可以在 Lush 提示符下互動式執行,也可以從文字檔案批次執行。

Hello World

[編輯 | 編輯原始碼]

首先,在終端中輸入 "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 補全

[編輯 | 編輯原始碼]

如果你不記得函式或變數的確切名稱,可以輸入其名稱的前幾個字母,然後按 Tab 鍵。如果存在無歧義的補全,Lush 將為你填入其餘字母。如果有多個符號符合條件,再次按 Tab 鍵將顯示可能的補全列表。例如,輸入 (prin 並按兩次 Tab 鍵將顯示以下內容。

 $ (prin
 prin            printf          print-window
 print           printrequester


命令歷史

[編輯 | 編輯原始碼]

按向上箭頭鍵將迴圈遍歷之前輸入的語句。

UNIX shell 命令

[編輯 | 編輯原始碼]

在 Lush 中,UNIX/Linux 專家熟悉的幾個常見的 shell 命令以函式的形式可用。例如

 $ (ls)
 =("pics" "todo.txt")
 $ (cd "pics")
 ="/home/mkg/pics"

Control 鍵快捷鍵

[編輯 | 編輯原始碼]

一些鍵盤快捷鍵對於 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 補全。


足夠的 LISP

[編輯 | 編輯原始碼]

Lush 直接從 LISP 繼承了其基於列表的語法。本節適用於沒有 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中求值的最後一個表示式。

數值陣列迴圈

[編輯 | 編輯原始碼]

大多數迭代將針對數值陣列(向量、矩陣或張量)的元素進行。雖然可以使用forwhile 迴圈來實現,但以下迭代器函式提供了更方便的替代方案。

idx-bloop

[編輯 | 編輯原始碼]

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-gloopidx-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-eloop 只是一個 idx-bloop,它遍歷最後一個索引而不是第一個索引。例如,當應用於矩陣時,idx-eloop 將遍歷其列,而不是其行。

陣列迭代注意事項

[編輯 | 編輯原始碼]
  • forwhile 迴圈不同,陣列迴圈表示式返回陣列列表中的第一個陣列,而不是主體中評估的任何表示式。
  • 子陣列是在堆上分配的,並且不應該期望它們在迴圈之外持續存在,即使您將它們分配給外部符號也是如此。這樣做會導致致命且神秘的記憶體錯誤。
華夏公益教科書