跳转到内容

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