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
循环不同,数组循环表达式返回数组列表中的第一个数组,而不是主体中评估的任何表达式。 - 子数组是在堆上分配的,并且不应该期望它们在循环之外持续存在,即使您将它们分配给外部符号也是如此。这样做会导致致命且神秘的内存错误。