Arc是一种Lisp方言的编程语言。它是由Paul Graham开发的,旨在简化Lisp的语法和语义,使其更易于使用和学习。Arc的设计目标是提供一种简洁而强大的编程语言,适用于快速开发Web应用程序。它具有动态类型、自动内存管理和强大的元编程能力。Arc还提供了许多内置函数和宏,以及用于处理字符串、列表和其他数据结构的丰富的库。虽然Arc在一些方面与传统的Lisp方言不同,但它仍然保留了Lisp的核心思想和功能。
Arc的官网:Arc Forum | Arc
学习Arc的网站:Arc 编程语言 (arclanguage.github.io)
以下为Arc语法教程,使用的自动翻译,就不再去修改里面的内容了。安装请参考:通过Racket 运行arc这个新型的lisp语言-CSDN博客[这是关于 Arc 的简短教程。它适用于几乎没有编程经验和没有 Lisp 经验的读者。因此,它也是对 Lisp 的介绍。Arc 程序由表达式组成。最简单的表达式是像数字和字符串这样的东西,它们可以自行计算。 arc> 25 25 arc> "foo" "foo" 弧度> 25 25 弧度>“foo” “foo” Several expressions enclosed within parentheses are also an expression. These are called lists. When a list is evaluated, the elements are evaluated from left to right, and the value of the first (presumably a function) is passed the values of the rest. Whatever it returns is returned as the value of the expression. 括在括号中的几个表达式也是一个表达式。这些称为列表。计算列表时,从左到右计算元素,并将第一个元素(可能是函数)的值传递给其余元素的值。无论它返回什么,都将作为表达式的值返回。 arc> (+ 1 2) 3 弧> (+ 1, 2) 3 Here's what just happened. First +, 1, and 2 were evaluated, returning the plus function, 1, and 2 respectively. 1 and 2 were then passed to the plus function, which returned 3, which was returned as the value of the whole expression. 这是刚刚发生的事情。计算第一个 +、1 和 2,分别返回加号函数 1 和 2。然后将 1 和 2 传递给 plus 函数,该函数返回 3,该函数作为整个表达式的值返回。 (Macros introduce a twist, because they transform lists before they're evaluated. We'll get to them later.) (宏引入了一个转折点,因为它们在评估列表之前对其进行了转换。我们稍后会谈到他们。 Since expression and evaluation are both defined recursively, programs can be as complex as you want: 由于表达式和求值都是递归定义的,因此程序可以根据需要复杂: arc> (+ (+ 1 2) (+ 3 (+ 4 5))) 15 弧> (+ (+ 1 2) (+ 3 (+ 4 5)))) 15 Putting the + before the numbers looks odd when you're used to writing "1 + 2," but it has the advantage that + can now take any number of arguments, not just two: 当你习惯于写“1 + 2”时,把 + 放在数字前面看起来很奇怪,但它的优点是 + 现在可以接受任意数量的参数,而不仅仅是两个: arc> (+) 0 arc> (+ 1) 1 arc> (+ 1 2) 3 arc> (+ 1 2 3) 6 弧度> (+) 0 弧> (+ 1) 1 弧> (+ 1 2) 3 弧> (+ 1 2 3) 6 This turns out to be a convenient property, especially when generating code, which is a common thing to do in Lisp. 事实证明,这是一个方便的属性,尤其是在生成代码时,这在 Lisp 中很常见。 Lisp dialects like Arc have a data type most languages don't: symbols. We've already seen one: + is a symbol. Symbols don't evaluate to themselves the way numbers and strings do. They return whatever value they've been assigned. 像 Arc 这样的 Lisp 方言具有大多数语言所没有的数据类型:符号。我们已经看到一个:+ 是一个符号。符号不会像数字和字符串那样对自己进行评估。它们返回分配给它们的任何值。 If we give foo the value 13, it will return 13 when evaluated: 如果我们给 foo 值 13,它在计算时将返回 13: arc> (= foo 13) 13 arc> foo 13 弧> (= foo 13) 13 弧> foo 13 You can turn off evaluation by putting a single quote character before an expression. So 'foo returns the symbol foo. 您可以通过在表达式前放置一个引号字符来关闭计算。所以 'foo 返回符号 foo。 arc> 'foo foo 弧>'foo foo Particularly observant readers may be wondering how we got away with using foo as the first argument to =. If the arguments are evaluated left to right, why didn't this cause an error when foo was evaluated? There are some operators that violate the usual evaluation rule, and = is one of them. Its first argument isn't evaluated. 特别细心的读者可能想知道我们是如何使用 foo 作为 = 的第一个参数的。如果参数是从左到右计算的,为什么在计算 foo 时没有导致错误?有一些运算符违反了通常的计算规则,= 就是其中之一。不评估它的第一个参数。 If you quote a list, you get back the list itself. 如果你引用一个列表,你就会得到列表本身。 arc> (+ 1 2) 3 arc> '(+ 1 2) (+ 1 2) 弧> (+ 1 2) 3 弧>'(+ 1 2) (+ 1 2) The first expression returns the number 3. The second, because it was quoted, returns a list consisting of the symbol + and the numbers 1 and 2. 第一个表达式返回数字 3。第二个,因为它被引用了,返回一个由符号 + 和数字 1 和 2 组成的列表。 You can build up lists with cons, which returns a list with a new element on the front: 您可以构建带有缺点的列表,这将返回一个前面带有新元素的列表: arc> (cons 'f '(a b)) (f a b) 弧> (cons 'f '(a b)) (f a b) It doesn't change the original list: 它不会更改原始列表: arc> (= x '(a b)) (a b) arc> (cons 'f x) (f a b) arc> x (a b) 弧> (= x '(a b)) (a b) 弧> (cons 'f x) (f a b) 弧> x (a b) The empty list is represented by the symbol nil, which is defined to evaluate to itself. So to make a list of one element you say: 空列表由符号 nil 表示,该符号被定义为对自身进行计算。因此,要列出一个元素,您可以说: arc> (cons 'a nil) (a) 弧> (缺点:无) (a) You can take lists apart with car and cdr, which return the first element and everything but the first element respectively: 您可以将列表与 car 和 cdr 分开,它们分别返回第一个元素和除第一个元素之外的所有元素: arc> (car '(a b c)) a arc> (cdr '(a b c)) (b c) 电弧> (汽车 '(a b c)) a 电弧> (cdr '(a b c)) (b c) To create a list with many elements use list, which does a series of conses: 要创建包含许多元素的列表,请使用 list,它执行一系列操作: arc> (list 'a 1 "foo" '(b)) (a 1 "foo" (b)) arc> (cons 'a (cons 1 (cons "foo" (cons '(b) nil)))) (a 1 "foo" (b)) 弧> (列出 'a 1 “foo” '(b)) (a 1 “foo” (b)) arc> (cons 'a (cons 1 (cons “foo” (cons '(b) nil)))) (a 1 “foo” (b)) Notice that lists can contain elements of any type. 请注意,列表可以包含任何类型的元素。 There are 4 parentheses at the end of that call to cons. How do Lisp programmers deal with this? They don't. You could add or subtract a right paren from that expression and most wouldn't notice. Lisp programmers don't count parens. They read code by indentation, not parens, and when writing code they let the editor match parens (use :set sm in vi, M-x lisp-mode in Emacs). 该调用的末尾有 4 个括号。Lisp程序员是如何处理这个问题的?他们没有。你可以从该表达式中添加或减去一个右 paren,大多数人不会注意到。Lisp程序员不计算parens。他们通过缩进而不是 parens 来读取代码,并且在编写代码时,他们让编辑器匹配 parens(在 vi 中使用 :set sm,在 Emacs 中使用 M-x lisp-mode)。 Like Common Lisp assignment, Arc's = is not just for variables, but can reach inside structures. So you can use it to modify lists: 与Common Lisp赋值一样,Arc的=不仅适用于变量,还可以到达结构内部。因此,您可以使用它来修改列表: arc> x (a b) arc> (= (car x) 'z) z arc> x (z b) 弧> x (a b) 弧> (= (汽车 x) 'z) z 弧> x (z b) Lists are useful in exploratory programming because they're so flexible. You don't have to commit in advance to exactly what a list represents. For example, you can use a list of two numbers to represent a point on a plane. Some would think it more proper to define a point object with two fields, x and y. But if you use lists to represent points, then when you expand your program to deal with n dimensions, all you have to do is make the new code default to zero for missing coordinates, and any remaining planar code will continue to work. 列表在探索性编程中很有用,因为它们非常灵活。您不必提前承诺列表所代表的确切内容。例如,您可以使用包含两个数字的列表来表示平面上的一个点。有些人会认为定义具有两个字段 x 和 y 的点对象更合适。但是,如果您使用列表来表示点,那么当您扩展程序以处理 n 个维度时,您所要做的就是将缺少坐标的新代码默认为零,并且任何剩余的平面代码将继续工作。 Or if you decide to expand in another direction and allow partially evaluated points, you can start using symbols representing variables as components of points, and once again, all the existing code will continue to work. 或者,如果您决定向另一个方向扩展并允许部分计算的点,则可以开始使用表示变量的符号作为点的组件,并且所有现有代码将继续工作。 In exploratory programming, it's as important to avoid premature specification as premature optimization. 在探索性编程中,避免过早的规范与过早的优化同样重要。 The most exciting thing lists can represent is code. The lists you build with cons are the same things programs are made out of. This means you can write programs that write programs. The usual way to do this is with something called a macro. We'll get to those later. First, functions. 列表可以代表的最令人兴奋的东西是代码。您构建的缺点列表与程序的组成相同。这意味着您可以编写编写程序的程序。通常的方法是使用称为宏的东西。我们稍后会谈到这些。第一,功能。 We've already seen some functions: +, cons, car, and cdr. You can define new ones with def, which takes a symbol to use as the name, a list of symbols representing the parameters, and then zero or more expressions called the body. When the function is called, those expressions will be evaluated in order with the symbols in the body temporarily set ("bound") to the corresponding argument. Whatever the last expression returns will be returned as the value of the call. 我们已经看到了一些函数:+、cons、car 和 cdr。 您可以使用 def 定义新的,它采用一个符号作为名称,一个表示参数的符号列表,然后是零个或多个称为正文的表达式。调用函数时,将按顺序计算这些表达式,并将正文中的符号临时设置为(“绑定”)到相应的参数。无论最后一个表达式返回什么,都将作为调用的值返回。 Here's a function that takes two numbers and returns their average: 下面是一个函数,它接受两个数字并返回它们的平均值: arc> (def average (x y) (/ (+ x y) 2)) #<procedure: average> arc> (average 2 4) 3 弧> (def 平均 (x y) (/ (+ x y) 2)) #<程序: 平均>弧> (平均 2 4) 3 The body of the function consists of one expression, (/ (+ x y) 2). It's common for functions to consist of one expression; in purely functional code (code with no side-effects) they always do. 函数的主体由一个表达式 (/ (+ x y) 2) 组成。函数通常由一个表达式组成;在纯函数式代码(没有副作用的代码)中,他们总是这样做。 Notice that def, like =, doesn't evaluate all its arguments. It is another of those operators with its own evaluation rule. 请注意,def 和 = 一样,不会计算其所有参数。它是另一个具有自己的评估规则的运算符。 What's the strange-looking object returned as the value of the def expression? That's what a function looks like. In Arc, as in most Lisps, functions are a data type, just like numbers or strings. 作为 def 表达式的值返回的看起来很奇怪的对象是什么?这就是函数的样子。在Arc中,就像在大多数Lisp中一样,函数是一种数据类型,就像数字或字符串一样。 As the literal representation of a string is a series of characters surrounded by double quotes, the literal representation of a function is a list consisting of the symbol fn, followed by its parameters, followed by its body. So you could represent a function to return the average of two numbers as: 由于字符串的文字表示是用双引号括起来的一系列字符,因此函数的文字表示是由符号 fn 组成的列表,后跟其参数,后跟其主体。因此,您可以表示一个函数来返回两个数字的平均值: arc> (fn (x y) (/ (+ x y) 2)) #<procedure> 弧> (fn (x y) (/ (+ x y) 2)) #<procedure> There's nothing semantically special about named functions as there is in some other languages. All def does is basically this: 命名函数在语义上没有什么特别之处,就像在其他一些语言中一样。def 所做的基本上是这样的: arc> (= average (fn (x y) (/ (+ x y) 2))) #<procedure: average> 弧> (= 平均值 (fn (x y) (/ (+ x y) 2))) #<过程:平均值> And of course you can use a literal function wherever you could use a symbol whose value is one, e.g. 当然,您可以在任何可以使用值为 1 的符号的地方使用文字函数,例如 arc> ((fn (x y) (/ (+ x y) 2)) 2 4) 3 弧> ((fn (x y) (/ (+ x y) 2)) 2 4) 3 This expression has three elements, (fn (x y) (/ (+ x y) 2)), which yields a function that returns averages, and the numbers 2 and 4. So when you evaluate all three expressions and pass the values of the second and third to the value of the first, you pass 2 and 4 to a function that returns averages, and the result is 3. 此表达式有三个元素 (fn (x y) (/ (+ x y) 2)),它产生一个返回平均值的函数,以及数字 2 和 4。因此,当您计算所有三个表达式并将第二个和第三个表达式的值传递给第一个表达式的值时,将 2 和 4 传递给返回平均值的函数,结果为 3。 There's one thing you can't do with functions that you can do with data types like symbols and strings: you can't print them out in a way that could be read back in. The reason is that the function could be a closure; displaying closures is a tricky problem. 有一件事你不能用函数做,你可以用数据类型(如符号和字符串)做:你不能以一种可以读回的方式打印出来。原因是该函数可能是一个闭包;显示闭包是一个棘手的问题。 In Arc, data structures can be used wherever functions are, and they behave as functions from indices to whatever's stored there. So to get the first element of a string you say: 在 Arc 中,数据结构可以在函数所在的任何位置使用,并且它们的行为就像从索引到存储在那里的任何函数一样。因此,要获取字符串的第一个元素,您可以说: arc> ("foo" 0) #\f 弧> (“foo” 0) #\f That return value is what a literal character looks like, incidentally. 顺便说一句,该返回值就是文字字符的样子。 Expressions with data structures in functional position also work as the first argument to =. 数据结构处于函数位置的表达式也可用作 = 的第一个参数。 arc> (= s "foo") "foo" arc> (= (s 0) #\m) #\m arc> s "moo" 弧> (= s “foo”) “foo” 弧> (= (s 0) #\m) #\m 弧> s “moo” There are two commonly used operators for establishing temporary variables, let and with. The first is for just one variable. 有两个常用的运算符用于建立临时变量,let 和 with。第一个仅针对一个变量。 arc> (let x 1 (+ x (* x 2))) 3 弧> (让 x 1 (+ x (* x 2))) 3 To bind multiple variables, use with. 若要绑定多个变量,请使用 with。 arc> (with (x 3 y 4)(sqrt (+ (expt x 2) (expt y 2)))) 5 弧> (带 (x 3 y 4) (sqrt (+ (expt x 2) (expt y 2))) 5 So far we've only had things printed out implicity as a result of evaluating them. The standard way to print things out in the middle of evaluation is with pr or prn. They take multiple arguments and print them in order; prn also prints a newline at the end. Here's a variant of average that tells us what its arguments were: 到目前为止,我们只打印出隐含性作为评估结果。在评估过程中打印出内容的标准方法是使用 pr 或 prn。他们采用多个参数并按顺序打印它们;PRN 还在末尾打印换行符。这是平均值的一个变体,它告诉我们它的论点是什么: arc> (def average (x y)(prn "my arguments were: " (list x y))(/ (+ x y) 2)) *** redefining average #<procedure: average> arc> (average 100 200) my arguments were: (100 200) 150 弧> (def average (x y) (prn “我的论点是: ” (列表 x y)) (/ (+ x y) 2)) *** 重新定义平均值 #<程序: 平均值> 弧> (平均值 100 200) 我的论点是: (100 200) 150 The standard conditional operator is if. Like = and def, it doesn't evaluate all its arguments. When given three arguments, it evaluates the first, and if that returns true, it returns the value of the second, otherwise the value of the third: 标准条件运算符为 if。像 = 和 def 一样,它不会评估它的所有参数。当给定三个参数时,它计算第一个参数,如果返回 true,则返回第二个参数的值,否则返回第三个参数的值: arc> (if (odd 1) 'a 'b) a arc> (if (odd 2) 'a 'b) b 弧> (如果 (奇数 1) 'a 'b) a 弧> (if (奇数 2) 'a 'b) b Returning true means returning anything except nil. Nil is conventionally used to represent falsity as well as the empty list. The symbol t (which like nil evaluates to itself) is often used to represent truth, but any value other than nil would serve just as well. 返回 true 意味着返回除 nil 之外的任何内容。Nil 通常用于表示虚假和空列表。符号 t(像 nil 一样计算自身)通常用于表示真理,但 nil 以外的任何值也同样适用。 arc> (odd 1) t arc> (odd 2) nil 弧>(奇数 1)t 弧>(奇数 2)无 It sometimes causes confusion to use the same thing for falsity and the empty list, but many years of Lisp programming have convinced me it's a net win, because the empty list is set-theoretic false, and many Lisp programs think in sets. 有时,将同样的东西用于虚假和空列表会引起混淆,但多年的Lisp编程使我相信这是一个净胜利,因为空列表是集合论的假的,许多Lisp程序都是在集合中思考的。 If the third argument is missing it defaults to nil. 如果缺少第三个参数,则默认为 nil。 arc> (if (odd 2) 'a) nil 弧> (if (奇数 2) 'a) nil An if with more than three arguments is equivalent to a nested if. 具有三个以上参数的 if 等效于嵌套的 if。 (if a b c d e) (如果 A、B、C、D、E) is equivalent to 相当于 (if ab(if cde)) (如果 a b (if c d e)) If you're used to languages with elseif, this pattern will be familiar. [1] 如果你习惯了使用 elseif 的语言,那么这种模式会很熟悉。[1] Each argument to if is a single expression, so if you want to do multiple things depending on the result of a test, combine them into one expression with do. if 的每个参数都是一个表达式,因此,如果您想根据测试结果执行多项操作,请将它们与 do 组合成一个表达式。 arc> (do (prn "hello") (+ 2 3)) hello 5 弧> (do (prn “hello”) (+ 2, 3)) hello 5 If you just want several expressions to be evaluated when some condition is true, you could say 如果你只想在某个条件为真时计算几个表达式,你可以说 (if a(do bc)) (如果 a (do b c)) but this situation is so common there's a separate operator for it. 但是这种情况非常普遍,因此需要单独的运算符。 (when abc) (当 a b c 时) The and and or operators are like conditionals because they don't evaluate more arguments than they have to. 和 和 或 运算符类似于条件句,因为它们不会计算比必须计算的更多的参数。 arc> (and nil(pr "you'll never see this")) nil arc> (和 nil (pr “你永远不会看到这个”)) nil The negation operator is called no, a name that also works when talking about nil as the empty list. Here's a function to return the length of a list: 否定运算符称为 no,当将 nil 作为空列表时,该名称也有效。下面是一个返回列表长度的函数: arc> (def mylen (xs)(if (no xs)0(+ 1 (mylen (cdr xs))))) #<procedure: mylen> 弧> (def mylen (xs) (if (no xs) 0 (+ 1 (mylen (cdr xs))))) #<procedure: mylen> If the list is nil the function will immediately return 0. Otherwise it returns 1 more than the length of the cdr of the list. 如果列表为 nil,则函数将立即返回 0。否则,它返回的 1 比列表的 cdr 长度多。 arc> (mylen nil) 0 arc> (mylen '(a b)) 2 弧> (Mylen Nil) 0 Arc> (Mylen '(a b)) 2 I called it mylen because there's already a function called len for this. You're welcome to redefine Arc functions, but redefining len this way might break code that depended on it, because len works on more than lists. 我称它为 mylen,因为已经有一个名为 len 的函数。欢迎您重新定义 Arc 函数,但以这种方式重新定义 len 可能会破坏依赖于它的代码,因为 len 处理的不仅仅是列表。 The standard comparison operator is is, which returns true if its arguments are identical or, if strings, have the same characters. 标准比较运算符是 is,如果其参数相同,或者字符串具有相同的字符,则返回 true。 arc> (is 'a 'a) t arc> (is "foo" "foo") t arc> (let x (list 'a) (is x x)) t arc> (is (list 'a) (list 'a)) nil 弧> (是 'a 'a) t 弧> (是 “foo” “foo”) t 弧> (let x (list 'a) (is x x)) t arc> (is (list 'a) (list 'a)) nil Note that is returns false for two lists with the same elements. There's another operator for that, iso (from isomorphic). 请注意,对于具有相同元素的两个列表,is 返回 false。还有另一个运算符,iso(来自同构)。 arc> (iso (list 'a) (list 'a)) t 弧> (ISO (列表 'A) (列表 'A)) t If you want to test whether something is one of several alternatives, you could say (or (is x y) (is x z) ...), but this situation is common enough that there's an operator for it. 如果你想测试某样东西是否是几个备选方案之一,你可以说(or (is x y) (is x z) ...),但这种情况很常见,以至于有一个运算符。 arc> (let x 'a (in x 'a 'b 'c)) t arc> (设 x 'a (in x 'a 'b 'c)) t The case operator takes alternating keys and expressions and returns the value of the expression after the key that matches. You can supply a final expression as the default. case 运算符采用交替键和表达式,并在匹配的键之后返回表达式的值。您可以提供最终表达式作为默认表达式。 arc> (def translate (sym)(case symapple 'mela onion 'cipolla'che?)) #<procedure: translate> arc> (translate 'apple) mela arc> (translate 'syzygy) che? arc> (def translate (sym) (case sym apple 'mela onion 'cipolla 'che?)) #<procedure: translate> arc> (translate 'apple) mela arc> (translate 'syzygy) che? Arc has a variety of iteration operators. For a range of numbers, use for. Arc 具有多种迭代运算符。对于数字范围,请使用 for。 arc> (for i 1 10 (pr i " ")) 1 2 3 4 5 6 7 8 9 10 nil 弧> (for i 1 10 (pr i “ ”)) 1 2 3 4 5 6 7 8 9 10 无 To iterate through the elements of a list or string, use each. 若要循环访问列表或字符串的元素,请使用每个元素。 arc> (each x '(a b c d e) (pr x " ")) a b c d e nil 弧> (每个 x '(a b c d e) (pr x “ ”)) a b c d e nil Those nils you see at the end each time are not printed out by the code in the loop. They're the return values of the iteration expressions. 你每次在最后看到的那些 nils 不是由循环中的代码打印出来的。它们是迭代表达式的返回值。 To continue iterating while some condition is true, use while. 若要在某个条件为真时继续迭代,请使用 while。 arc> (let x 10(while (> x 5)(= x (- x 1))(pr x))) 98765nil 弧> (让 x 10 (而 (> x 5) (= x (- x 1)) (pr x))) 98765nil There's also a more general loop operator that's similar to the C for operator and tends to be rarely used in practice, and a simple repeat operator for doing something n times: 还有一个更通用的循环运算符,类似于 C for 运算符,在实践中很少使用,还有一个简单的重复运算符,用于做 n 次的事情: arc> (repeat 5 (pr "la ")) la la la la la nil 弧> (重复 5 (pr “la ”)) la la la la la nil The map function takes a function and a list and returns the result of applying the function to successive elements. map 函数接受一个函数和一个列表,并返回将该函数应用于连续元素的结果。 arc> (map (fn (x) (+ x 10)) '(1 2 3)) (11 12 13) 弧> (地图 (fn (x) (+ x 10)) '(1 2 3)) (11 12 13) Actually it can take any number of sequences, and keeps going till the shortest runs out: 实际上,它可以接受任意数量的序列,并一直持续到最短的用完为止: arc> (map + '(1 2 3 4) '(100 200 300)) (101 202 303) 弧> (地图 + '(1 2 3 4) '(100 200 300)) (101 202 303) Since functions of one argument are so often used in Lisp programs, Arc has a special notation for them. [... _ ...] is an abbreviation for (fn (_) (... _ ...)). So our first map example could have been written 由于一个参数的函数在 Lisp 程序中经常使用,因此 Arc 对它们有一个特殊的表示法。[... _ ...]是 (fn (_) (... _ ...)) 的缩写。因此,我们的第一个地图示例可以编写 arc> (map [+ _ 10] '(1 2 3)) (11 12 13) 弧> (地图 [+ _ 10] '(1 2 3)) (11 12 13) Removing variables is a particularly good way to make programs shorter. An unnecessary variable increases the conceptual load of a program by more than just what it adds to the length. 删除变量是缩短程序的一种特别好的方法。一个不必要的变量会增加程序的概念负载,而不仅仅是它增加的长度。 You can compose functions by putting a colon between the names. I.e. (foo:bar x y) is equivalent to (foo (bar x y)). Composed functions are convenient as arguments. 您可以通过在名称之间放置冒号来组合函数。即 (foo:bar x y) 等价于 (foo (bar x y))。组合函数作为参数很方便。 arc> (map odd:car '((1 2) (4 5) (7 9))) (t nil t) arc> (map odd:car '(((1 2) (4 5) (7 9))) (t nil t) You can also negate a function by putting a tilde (~) before the name: 您还可以通过在名称前添加波浪号 (~) 来否定函数: arc> (map ~odd '(1 2 3 4 5)) (nil t nil t nil) arc> (map ~odd '(1 2 3 4 5)) (nil t nil t nil) There are a number of functions like map that apply functions to successive elements of a sequence. The most commonly used is keep, which returns the elements satisfying some test. 有许多函数(如 map)将函数应用于序列的连续元素。最常用的是 keep,它返回满足某些测试的元素。 arc> (keep odd '(1 2 3 4 5 6 7)) (1 3 5 7) 弧> (保持奇数 '(1 2 3 4 5 6 7)) (1 3 5 7) Others include rem, which does the opposite of keep; all, which returns true if the function is true of every element; some, which returns true if the function is true of any element; pos, which returns the position of the first element for which the function returns true; and trues, which returns a list of all the non-nil return values: 其他包括 rem,它与 keep 相反;all,如果每个元素的函数都为 true,则返回 true;some,如果函数对任何元素为 true,则返回 true;pos,它返回函数返回 true 的第一个元素的位置;和 trues,它返回所有非 nil 返回值的列表: arc> (rem odd '(1 2 3 4 5 6)) (2 4 6) arc> (all odd '(1 3 5 7)) t arc> (some even '(1 3 5 7)) nil arc> (pos even '(1 2 3 4 5)) 1 arc> (trues [if (odd _) (+ _ 10)] '(1 2 3 4 5)) (11 13 15) 弧> (rem odd '(1 2 3 4 5 6)) (2 4 6) 弧> (所有奇数 '(1 3 5 7)) t 弧> (有些偶数 '(1 3 5 7)) 无弧> (pos 偶数 '(1 2 3 4 5)) 1 弧> (真 [if (奇数 _) (+ _ 10)] '(1 2 3 4 5)) (11 13 15) If functions like this are given a first argument that isn't a function, it's treated like a function that tests for equality to that: 如果像这样的函数被赋予一个不是函数的第一个参数,则它被视为一个测试与该函数相等的函数: arc> (rem 'a '(a b a c u s)) (b c u s) arc> (rem 'a '(a b a c u s)) (b c u s) and they all work on strings as well as lists. 它们都适用于字符串和列表。 arc> (rem #\a "abacus") "bcus" arc> (rem #\a “abacus”) “因为” Lists can be used to represent a wide variety of data structures, but if you want to store key/value pairs efficiently, Arc also has hash tables. 列表可用于表示各种数据结构,但如果您想有效地存储键/值对,Arc 也有哈希表。 arc> (= airports (table)) #hash() arc> (= (airports "Boston") 'bos) bos arc> (= 机场 (表)) #hash() arc> (= (机场“波士顿”) 'bos) bos If you want to create a hash table filled with values, you can use listtab, which takes a list of key/value pairs and returns the corresponding hash table. 如果要创建充满值的哈希表,可以使用 listtab,它获取键/值对列表并返回相应的哈希表。 arc> (let h (listtab '((x 1) (y 2)))(h 'y)) 2 arc> (let h (listtab '((x 1) (y 2))) (h 'y)) 2 There's also an abbreviated form where you don't need to group the arguments or quote the keys. 还有一个缩写形式,您不需要对参数进行分组或引用键。 arc> (let h (obj x 1 y 2)(h 'y)) 2 弧> (让 h (obj x 1 y 2) (h 'y)) 2 Like lists and strings, hash tables can be used wherever functions are. 与列表和字符串一样,哈希表可以在函数所在的任何地方使用。 arc> (= codes (obj "Boston" 'bos "San Francisco" 'sfo "Paris" 'cdg)) #hash(("Boston" . bos) ("Paris" . cdg) ("San Francisco" . sfo)) arc> (map codes '("Paris" "Boston" "San Francisco")) (cdg bos sfo) arc> (= codes (obj “Boston” 'bos “旧金山” 'sfo “巴黎” 'cdg)) #hash((“波士顿” . bos) (“巴黎” . cdg) (“旧金山” . sfo)) arc> (map codes '(“Paris”, “Boston”, “San Francisco”)) (cdg bos sfo) The function keys returns the keys in a hash table, and vals returns the values. 函数键返回哈希表中的键,vals 返回值。 arc> (keys codes) ("Boston" "Paris" "San Francisco") arc> (keys codes) (“Boston”, “Paris”, “San Francisco”) There is a function called maptable for hash tables that is like map for lists, except that it returns the original table instead of a new one. 对于哈希表,有一个名为 maptable 的函数,它类似于列表的 map,只是它返回原始表而不是新表。 arc> (maptable (fn (k v) (prn v " " k))codes) sfo San Francisco cdg Paris bos Boston #hash(("Boston" . bos) ("Paris" . cdg) ("San Francisco" . sfo)) arc> (maptable (fn (k v) (prn v “ ” k)) codes) sfo San Francisco cdg Paris bos Boston #hash((“Boston” .bos) (“巴黎” . cdg) (“旧金山” . sfo)) [Note: Like functions, hash tables can't be printed out in a way that can be read back in. We hope to fix that though.] [注意:与函数一样,哈希表不能以可以读回的方式打印出来。不过,我们希望能解决这个问题。 There is a tradition in Lisp going back to McCarthy's 1960 paper [2] of using lists to represent key/value pairs: Lisp 有一个传统可以追溯到 McCarthy 1960 年的论文 [2],即使用列表来表示键/值对: arc> (= codes '(("Boston" bos) ("Paris" cdg) ("San Francisco" sfo))) (("Boston" bos) ("Paris" cdg) ("San Francisco" sfo)) arc> (= codes '((“Boston” bos) (“Paris” cdg) (“San Francisco” sfo))) ((“Boston” bos) (“Paris” cdg) (“San Francisco” sfo)) This is called an association list, or alist for short. I once thought alists were just a hack, but there are many things you can do with them that you can't do with hash tables, including sort them, build them up incrementally in recursive functions, have several that share the same tail, and preserve old values. 这称为关联列表,简称 alist。我曾经认为 alists 只是一个 hack,但你可以用它们做很多事情,而你不能用哈希表做,包括对它们进行排序,在递归函数中增量构建它们,有几个共享相同的尾巴,并保留旧值。 The function alref returns the first value corresponding to a key in an alist: 函数 alref 返回与 alist 中的键对应的第一个值: arc> (alref codes "Boston") bos arc> (alref codes “Boston”) bos There are a couple operators for building strings. The most general is string, which takes any number of arguments and mushes them into a string: 有几个运算符用于构建字符串。最通用的是字符串,它接受任意数量的参数并将它们混成一个字符串: arc> (string 99 " bottles of " 'bee #\r) "99 bottles of beer" arc> (string 99 “ bottles of ” 'bee #\r) “99 bottles of beer” Every argument will appear as it would look if printed out by pr, except nil, which is ignored. 每个参数都将按照 pr 打印出来的样子显示,但 nil 除外,它被忽略。 There's also tostring, which is like do except any output generated during the evaluation of its body is sent to a string, which is returned as the value of the whole expression. 还有 tostring,它类似于 do,只是在评估其主体期间生成的任何输出都会发送到字符串,该字符串作为整个表达式的值返回。 arc> (tostring (prn "domesday")(prn "book")) "domesday\nbook\n" arc> (tostring (prn “Domesday”) (prn “book”)) “Domesday\nbook\n” You can find the types of things using type, and convert them to new types using coerce. 您可以使用类型找到事物的类型,并使用强制将它们转换为新类型。 arc> (map type (list 'foo 23 23.5 '(a) nil car "foo" #\a)) (sym int num cons sym fn string char) arc> (coerce #\A 'int) 65 arc> (coerce "foo" 'cons) (#\f #\o #\o) arc> (coerce "99" 'int) 99 arc> (coerce "99" 'int 16) 153 arc> (map type (list 'foo 23 23.5 '(a) nil car “foo” #\a)) (sym int num cons sym fn string char) arc> (coerce #\A 'int) 65 arc> (coerce “foo” 'cons) (#\f #\o #\o) arc> (coerce “99” 'int) 99 arc> (coerce “99” 'int 16) 153 The push and pop operators treat list as stacks, pushing a new element on the front and popping one off respectively. push 和 pop 运算符将列表视为堆栈,分别在前面推送一个新元素并弹出一个元素。 arc> (= x '(c a b)) (c a b) arc> (pop x) c arc> x (a b) arc> (push 'f x) (f a b) arc> x (f a b) 弧> (= x '(c a b)) (c a b) 弧> (pop x) c 弧> x (a b) 弧> (push 'f x) (f a b) 弧> x (f a b) Like =, they work within structures, not just on variables. 像 = 一样,它们在结构中起作用,而不仅仅是在变量上起作用。 arc> (push 'l (cdr x)) (l a b) arc> x (f l a b) 弧> (推 'l (cdr x)) (l a b) 弧> x (f l a b) To increment or decrement use ++ or --: 要递增或递减,请使用 ++ 或 --: arc> (let x '(1 2 3) (++ (car x))x) (2 2 3) 弧> (让 x '(1 2 3) (++ (汽车 x)) x) (2 2 3) There's also a more general operator called zap that changes something to the result any function returns when applied to it. I.e. (++ x) is equivalent to (zap [+ _ 1] x). 还有一个更通用的运算符,称为 zap,它可以更改任何函数在应用于它时返回的结果。即 (++ x) 等价于 (zap [+ _ 1] x)。 The sort function returns a copy of a sequence sorted according to the function given as the first argument. 排序函数返回根据作为第一个参数给出的函数排序的序列的副本。 arc> (sort < '(2 9 3 7 5 1)) (1 2 3 5 7 9) 弧> (排序 < '(2 9 3 7 5 1)) (1 2 3 5 7 9) It doesn't change the original, so if you want to sort the value of a particular variable (or place within a structure), use zap: 它不会更改原始变量,因此,如果要对特定变量(或结构中的位置)的值进行排序,请使用 zap: arc> (= x '(2 9 3 7 5 1)) (2 9 3 7 5 1) arc> (zap [sort < _] x) (1 2 3 5 7 9) arc> x (1 2 3 5 7 9) 弧> (= x '(2 9 3 7 5 1)) (2 9 3 7 5 1) 弧> (zap [sort < _] x) (1 2 3 5 7 9) 弧> x (1 2 3 5 7 9) If you want to modify a sorted list by inserting a new element at the right place, use insort: 如果要通过在正确的位置插入新元素来修改排序列表,请使用 insort: arc> (insort < 4 x) (1 2 3 4 5 7 9) arc> x (1 2 3 4 5 7 9) 弧> (insort < 4 x) (1 2 3 4 5 7 9) 弧> x (1 2 3 4 5 7 9) In practice the things one needs to sort are rarely just lists of numbers. More often you'll need to sort things according to some property other than their value, e.g. 在实践中,需要排序的东西很少只是数字列表。更常见的是,您需要根据其价值以外的某些属性对事物进行排序,例如 arc> (sort (fn (x y) (< (len x) (len y)))'("orange" "pea" "apricot" "apple")) ("pea" "apple" "orange" "apricot") arc> (sort (fn (x y) (< (len x) (len y))) '(“orange”, “pea”, “apricot”, “apple”)) (“pea”, “apple”, “orange”, “apricot”) Arc's sort is stable, meaning the relative positions of elements judged equal by the comparison function won't change: Arc 的排序是稳定的,这意味着通过比较函数判断相等的元素的相对位置不会改变: arc> (sort (fn (x y) (< (len x) (len y)))'("aa" "bb" "cc")) ("aa" "bb" "cc") arc> (sort (fn (x y) (< (len x) (len y))) '(“aa”, “bb”, “cc”)) (“aa”, “bb”, “cc”) Since comparison functions other than > or < are so often needed, Arc has a compare function to build them: 由于经常需要 > 或 < 以外的比较函数,因此 Arc 有一个比较函数来构建它们: arc> (sort (compare < len)'("orange" "pea" "apricot" "apple")) ("pea" "apple" "orange" "apricot") arc> (sort (compare < len) '(“orange”, “pea”, “apricot”, “apple”)) (“pea”, “apple”, “orange”, “apricot”) We've seen several functions so far that take optional arguments or varying numbers of arguments. To make a parameter optional, just say (o x) instead of x. Optional parameters default to nil. 到目前为止,我们已经看到几个函数采用可选参数或不同数量的参数。要使参数可选,只需说 (o x) 而不是 x。可选参数默认为 nil。 arc> (def greet (name (o punc))(string "hello " name punc)) #<procedure: greet> arc> (greet 'joe) "hello joe" arc> (greet 'joe #\!) "hello joe!" arc> (def greet (name (o punc)) (string “hello ” name punc)) #<procedure: greet> arc> (greet 'joe) “hello joe” arc> (greet 'Joe #\!) ”你好乔! Functions can have as many optional parameters as you want, but they have to come at the end of the parameter list. 函数可以具有任意数量的可选参数,但它们必须位于参数列表的末尾。 If you put an expression after the name of an optional parameter, it will be evaluated if necessary to produce a default value. The expression can refer to preceding parameters. 如果将表达式放在可选参数的名称之后,则将在必要时对其进行计算以生成默认值。表达式可以引用前面的参数。 arc> (def greet (name (o punc (case name who #\? #\!)))(string "hello " name punc)) *** redefining greet #<procedure: greet> arc> (greet 'who) "hello who?" arc> (def greet (name (o punc (case name who #\? #\!))(字符串 “hello ” name punc))重新定义问候 #<程序:问候>弧>(问候“谁”)“你好谁? To make a function that takes any number of arguments, put a period and a space before the last parameter, and it will get bound to a list of the values of all the remaining arguments: 要创建一个接受任意数量的参数的函数,请在最后一个参数之前放置一个句点和一个空格,它将绑定到所有剩余参数的值列表: arc> (def foo (x y . z) (list x y z)) #<procedure: foo> arc> (foo (+ 1 2) (+ 3 4) (+ 5 6) (+ 7 8)) (3 7 (11 15)) 弧> (def foo (x y . z) (列表 x y z)) #<程序: foo> 弧> (foo (+ 1 2) (+ 3 4) (+ 5 6) (+ 7 8)) (3 7 (11 15)) This type of parameter is called a "rest parameter" because it gets the rest of the arguments. If you want all the arguments to a function to be collected in one parameter, just use it in place of the whole parameter list. 这种类型的参数被称为“rest 参数”,因为它获取其余的参数。如果您希望将函数的所有参数收集在一个参数中,只需使用它来代替整个参数列表即可。 (These conventions are not as random as they seem. The parameter list mirrors the form of the arguments, and a list terminated by something other than nil is represented as e.g. (a b . c).) (这些约定并不像看起来那么随机。参数列表镜像参数的形式,以 nil 以外的其他名称结尾的列表表示为 (a b . c)。 To supply a list of arguments to a function, use apply: 若要向函数提供参数列表,请使用 apply: arc> (apply + '(1 2 3)) 6 弧> (应用 + '(1 2 3)) 6 Now that we have rest parameters and apply, we can write a version of average that takes any number of arguments. 现在我们有了 rest 参数并应用,我们可以编写一个接受任意数量参数的 average 版本。 arc> (def average args (/ (apply + args) (len args))) #<procedure: average> arc> (average 1 2 3) 2 arc> (def average args (/ (apply + args) (len args))) #<procedure: average> arc> (average 1 2 3) 2 We know enough now to start writing macros. Macros are basically functions that generate code. Of course, generating code is easy; just call list. 我们现在知道的足够多了,可以开始编写宏了。宏基本上是生成代码的函数。当然,生成代码很容易;只是呼叫列表。 arc> (list '+ 1 2) (+ 1 2) 弧> (列表 '+ 1 2) (+ 1 2) What macros offer is a way of getting code generated this way into your programs. Here's a (rather stupid) macro definition: 宏提供的是一种以这种方式生成的代码进入程序的方法。这是一个(相当愚蠢的)宏定义: arc> (mac foo () (list '+ 1 2)) *** redefining foo #3(tagged mac #<procedure>) arc> (mac foo () (list '+ 1 2)) *** 重新定义 foo #3(标记 mac #<procedure>) Notice that a macro definition looks exactly like a function definition, but with def replaced by mac. 请注意,宏定义看起来与函数定义完全相同,但 def 被 mac 替换。 What this macro says is that whenever the expression (foo) occurs in your code, it shouldn't be evaluated in the normal way like a function call. Instead it should be replaced by the result of evaluating the body of the macro definition, (list '+ 1 2). This is called the "expansion" of the macro call. 这个宏说的是,每当表达式 (foo) 出现在你的代码中时,都不应该像函数调用那样以正常方式计算它。相反,它应该被计算宏定义主体的结果所取代(列表 '+ 1 2)。这称为宏调用的“扩展”。 In other words, if you've defined foo as above, putting (foo) anywhere in your code is equivalent to putting (+ 1 2) there. 换句话说,如果你如上所述定义了 foo,那么将 (foo) 放在代码中的任何位置就等同于将 (+ 1 2) 放在那里。 arc> (+ 10 (foo)) 13 弧度> (+ 10 (foo)) 13 This is a rather useless macro, because it doesn't take any arguments. Here's a more useful one: 这是一个相当无用的宏,因为它不需要任何参数。这是一个更有用的方法: arc> (mac when (test . body)(list 'if test (cons 'do body))) *** redefining when #3(tagged mac #<procedure>) arc> (mac when (test . body) (list 'if test (cons 'do body))) *** redefining when #3(tagged mac #<procedure>) We've just redefined the built-in when operator. That would ordinarily be an alarming idea, but fortunately the definition we supplied is the same as the one it already had. 我们刚刚重新定义了内置的 when 运算符。这通常是一个令人震惊的想法,但幸运的是,我们提供的定义与它已经拥有的定义相同。 arc> (when 1 (pr "hello ")2) hello 2 弧> (当 1 (pr “hello ”) 2) hello 2 What the definition above says is that when you have to evaluate an expression whose first element is when, replace it by the result of applying 上面的定义是,当您必须计算第一个元素为 when 的表达式时,请将其替换为应用的结果 (fn (test . body) (list 'if test (cons 'do body))) (fn (test . body) (list 'if test (cons 'do body))) to the arguments. Let's try it by hand and see what we get. 到参数。让我们手工尝试一下,看看我们得到了什么。 arc> (apply (fn (test . body) (list 'if test (cons 'do body)))'(1 (pr "hello ") 2)) (if 1 (do (pr "hello ") 2)) arc> (apply (fn (test . body) (list 'if test (cons 'do body))) '(1 (pr “hello ”) 2)) (if 1 (do (pr “hello ”) 2)) So when Arc has to evaluate 因此,当 Arc 必须评估时 (when 1 (pr "hello ") 2) (当 1 (pr “hello ”) 2) the macro we defined transforms that into 我们定义的宏将其转换为 (if 1 (do (pr "hello ") 2)) (如果 1 (do (pr “hello ”) 2)) first, and when that in turn is evaluated, it produces the behavior we saw above. 首先,当反过来对其进行评估时,它会产生我们上面看到的行为。 Building up expressions using calls to list and cons can get unwieldy, so most Lisp dialects have an abbreviation called backquote that makes generating lists easier. 使用调用列表和缺点来构建表达式可能会变得笨拙,因此大多数 Lisp 方言都有一个称为 backquote 的缩写,这使得生成列表变得更加容易。 If you put a single open-quote character (`) before an expression, it turns off evaluation just like the ordinary quote (') does, 如果在表达式前放置一个左引号字符 ('),它会像普通引号 (') 一样关闭计算, arc> `(a b c) (a b c) 弧>'(a b c) (a b c) except that if you put a comma before an expression within the list, evaluation gets turned back on for that expression. 但是,如果在列表中的表达式之前放置逗号,则该表达式的计算将重新打开。 arc> (let x 2`(a ,x c)) (a 2 c) 弧> (让 x 2'(a ,x c)) (a 2 c) A backquoted expression is like a quoted expression with holes in it. 反引号表达式就像带引号的表达式一样,里面有孔。 You can also put a comma-at (,@) in front of anything within a backquoted expression, and in that case its value (which must be a list) will get spliced into whatever list you're currently in. 您还可以在反引号表达式中的任何内容前面放置一个逗号 (,@),在这种情况下,它的值(必须是列表)将被拼接到您当前所在的任何列表中。 arc> (let x '(1 2)`(a ,@x c)) (a 1 2 c) 弧> (设 x '(1 2) '(a ,@x c)) (a 1 2 c) With backquote we can make the definition of when more readable. 使用反引号,我们可以使 when 的定义更具可读性。 (mac when (test . body)`(if ,test (do ,@body))) (mac when (test . body) '(if ,test (do ,@body))) In fact, this is the definition of when in the Arc source. 实际上,这是 Arc 源中 when 的定义。 One of the keys to understanding macros is to remember that macro calls aren't function calls. Macro calls look like function calls. Macro definitions even look a lot like function definitions. But something fundamentally different is happening. You're transforming code, not evaluating it. Macros live in the land of the names, not the land of the things they refer to. 理解宏的关键之一是记住宏调用不是函数调用。宏调用类似于函数调用。宏定义甚至看起来很像函数定义。但一些根本不同的事情正在发生。您是在转换代码,而不是评估它。宏生活在名字的土地上,而不是他们所指的事物的土地上。 For example, consider this definition of repeat: 例如,考虑重复的以下定义: arc> (mac repeat (n . body)`(for x 1 ,n ,@body)) #3(tagged mac #<procedure>) arc> (mac repeat (n . body) '(for x 1 ,n ,@body)) #3(标记 mac #<procedure>) Looks like it works, right? 看起来它有效,对吧? arc> (repeat 3 (pr "blub ")) blub blub blub nil 弧> (重复 3 (pr “blub ”)) blub blub blub nil But if you use it in certain contexts, strange things happen. 但是,如果您在某些情况下使用它,就会发生奇怪的事情。 arc> (let x "blub " (repeat 3 (pr x))) 123nil 弧> (让 x “blub ” (重复 3 (pr x))) 123nil We can see what's going wrong if we look at the expansion. The code above is equivalent to 如果我们看一下扩张,我们可以看到出了什么问题。上面的代码等价于 (let x "blub "(for x 1 3 (pr x))) (让 x “blub ” (for x 1 3 (pr x))) Now the bug is obvious. The macro uses the variable x to hold the count while iterating, and that gets in the way of the x we're trying to print. 现在这个错误很明显。宏在迭代时使用变量 x 来保存计数,这妨碍了我们尝试打印的 x。 Some people worry unduly about this kind of bug. It caused the Scheme committee to adopt a plan for "hygienic" macros that was probably a mistake. It seems to me that the solution is not to encourage the noob illusion that macro calls are function calls. People writing macros need to remember that macros live in the land of names. Naturally in the land of names you have to worry about using the wrong names, just as in the land of values you have to remember not to use the wrong values-- for example, not to use zero as a divisor. 有些人过分担心这种错误。这导致计划委员会通过了一项“卫生”宏计划,这可能是一个错误。在我看来,解决方案不是鼓励新手的错觉,即宏调用是函数调用。编写宏的人需要记住,宏生活在名称的土地上。当然,在名称之国,你必须担心使用错误的名字,就像在价值之国,你必须记住不要使用错误的值——例如,不要使用零作为除数。 The way to fix repeat is to use a symbol that couldn't occur in source code instead of x. In Arc you can get one by calling the function uniq. So the correct definition of repeat (and in fact the one in the Arc source) is 修复重复的方法是使用源代码中不会出现的符号而不是 x。在 Arc 中,可以通过调用函数 uniq 来获取一个。因此,重复(实际上是 Arc 源中的那个)的正确定义是 (mac repeat (n . body)`(for ,(uniq) 1 ,n ,@body)) (mac repeat (n . body) '(for ,(uniq) 1 ,n ,@body)) If you need one or more uniqs for use in a macro, you can use w/uniq, which takes either a variable or list of variables you want bound to uniqs. Here's the definition of a variant of do called do1 that's like do but returns the value of its first argument instead of the last (useful if you want to print a message after something happens, but return the something, not the message): 如果需要一个或多个 uniq 在宏中使用,则可以使用 w/uniq,它采用要绑定到 uniq 的变量或变量列表。下面是名为 do1 的 do 变体的定义,它类似于 do,但返回其第一个参数的值而不是最后一个参数(如果您想在发生某些事情后打印消息,但返回 something,而不是消息,则很有用): (mac do1 args(w/uniq g`(let ,g ,(car args),@(cdr args),g))) (Mac do1 args (w/uniq g '(let ,g ,(car args) ,@(cdr args) ,g))) Sometimes you actually want to "capture" variables, as it's called, in macro definitions. The following variant of when, which binds the variable it to the value of the test, turns out to be very useful: 有时,您实际上希望在宏定义中“捕获”变量,正如它所称的那样。以下 when 变体将变量 it 绑定到测试值,结果证明非常有用: (mac awhen (expr . body)`(let it ,expr (if it (do ,@body)))) (mac awhen (expr . body) '(let it ,expr (if it (do ,@body)))) In a sense, you now know all about macros-- in the same sense that, if you know the axioms in Euclid, you know all the theorems. A lot follows from these simple ideas, and it can take years to explore the territory they define. At least, it took me years. But it's a path worth following. Because macro calls can expand into further macro calls, you can generate massively complex expressions with them-- code you would have had to write by hand otherwise. And yet programs built up out of layers of macros turn out to be very manageable. I wouldn't be surprised if some parts of my code go through 10 or 20 levels of macroexpansion before the compiler sees them, but I don't know, because I've never had to look. 从某种意义上说,你现在对宏了如指掌——从同样的意义上说,如果你知道欧几里得的公理,你就知道了所有的定理。从这些简单的想法中得出了很多结论,探索它们定义的领域可能需要数年时间。至少,我花了好几年的时间。但这是一条值得走的道路。由于宏调用可以扩展到进一步的宏调用,因此您可以使用它们生成大量复杂的表达式 - 否则您将不得不手动编写代码。然而,由宏层构建的程序被证明是非常易于管理的。如果我的代码的某些部分在编译器看到它们之前经历了 10 或 20 级宏扩展,我不会感到惊讶,但我不知道,因为我从来没有看过。 One of the things you'll discover as you learn more about macros is how much day-to-day coding in other languages consists of manually generating macroexpansions. Conversely, one of the most important elements of learning to think like a Lisp programmer is to cultivate a dissatisfaction with repetitive code. When there are patterns in source code, the response should not be to enshrine them in a list of "best practices," or to find an IDE that can generate them. Patterns in your code mean you're doing something wrong. You should write the macro that will generate them and call that instead. 随着您对宏的了解越来越多,您会发现的一件事是,其他语言的日常编码中有多少是手动生成宏扩展的。相反,学习像Lisp程序员一样思考的最重要因素之一就是培养对重复代码的不满。当源代码中存在模式时,响应不应该是将它们包含在“最佳实践”列表中,或者找到可以生成它们的 IDE。代码中的模式意味着你做错了什么。您应该编写将生成它们的宏并改为调用它。 Now that you've learned the basics of Arc programming, the best way to learn more about the language is to try writing some programs in it. Here's how to write the hello-world of web apps: 现在你已经学习了 Arc 编程的基础知识,了解更多关于该语言的最好方法是尝试用它编写一些程序。下面介绍了如何编写 Web 应用的 hello-world: arc> (defop hello req (pr "hello world")) #<procedure:gs1430> arc> (asv) ready to serve port 8080 Arc> (defop hello req (pr “hello world”)) #<procedure:GS1430> arc> (ASV) 准备为端口 8080 提供服务 If you now go to http://localhost:8080/hello your new web app will be waiting for you. 如果您现在转到 http://localhost:8080/hello 您的新 Web 应用程序将等着您。 Here are a couple slightly more complex hellos that hint at the convenience of macros that store closures on the server: 下面是几个稍微复杂一点的问候,它们暗示了在服务器上存储闭包的宏的便利性: (defop hello2 req(w/link (pr "there") (pr "here"))) (defop hello2 req (w/link (pr “那里”) (pr “这里”))) (defop hello3 req(w/link (w/link (pr "end")(pr "middle"))(pr "start"))) (defop hello3 req (w/link (w/link (pr “end”) (pr “middle”)) (pr “start”))) (defop hello4 req(aform [w/link (pr "you said: " (arg _ "foo"))(pr "click here")](input "foo")(submit))) (defop hello4 req (aform [w/link (pr “你说的: ” (arg _ “foo”)) (pr “点击这里”)] (输入 “foo”) (提交))) See the sample application in blog.arc for ideas about how to make web apps that do more. 请参阅 blog.arc 中的示例应用程序,了解如何制作功能更强的 Web 应用程序。 We now know enough Arc to read the definitions of some of the predefined functions. Here are a few of the simpler ones. 我们现在知道了足够的 Arc 来读取一些预定义函数的定义。这里有一些更简单的方法。 (def cadr (xs) (car (cdr xs))) (def cadr (xs) (汽车 (CDR xs))) (def no (x) (is x nil)) (定义编号 (x) (is x nil)) (def list args args) (def list args args) (def isa (x y) (is (type x) y)) (def isa (x y) (is (type x) y)) (def firstn (n xs)(if (and (> n 0) xs)(cons (car xs) (firstn (- n 1) (cdr xs)))nil)) (def firstn (n xs) (if (and (> n 0) xs) (cons (car xs) (firstn (- n 1) (cdr xs))) nil)) (def nthcdr (n xs) (if (> n 0)(nthcdr (- n 1) (cdr xs))xs)) (def nthcdr (n xs) (if (> n 0) (nthcdr (- n 1) (cdr xs)) xs)) (def tuples (xs (o n 2))(if (no xs)nil(cons (firstn n xs)(tuples (nthcdr n xs) n)))) (def 元组 (xs (o n 2)) (if (no xs) nil (cons (firstn n xs) (tuples (nthcdr n xs) n)))) (def trues (f seq) (rem nil (map f seq))) (def trues (f seq) (rem nil (map f seq))) (mac unless (test . body)`(if (no ,test) (do ,@body))) (mac 除非 (test . body) '(if (no ,test) (do ,@body))) (mac n-of (n expr)(w/uniq ga`(let ,ga nil(repeat ,n (push ,expr ,ga))(rev ,ga)))) (Mac n-of (n expr) (w/uniq ga '(let ,ga nil (repeat ,n (push ,expr ,ga)) (rev ,ga)))) These definitions are taken from arc.arc. As its name suggests, reading that file is a good way to learn more about both Arc and Arc programming techniques. Nothing in it is used before it's defined; it is an exercise in building the part of the language written in Arc up from the "axioms" defined in ac.scm. I hoped this would yield a simple language. But since this is also the source code of Arc, I've tried to balance simplicity with efficiency. The definitions aren't mathematically minimal if that would be insanely inefficient; I tried that once, and they were. 这些定义取自 arc.arc。顾名思义,阅读该文件是了解有关 Arc 和 Arc 编程技术的更多信息的好方法。在定义之前,不会使用其中的任何内容;它是从 ac.scm 中定义的“公理”构建用 Arc 编写的语言部分的练习。我希望这将产生一种简单的语言。但由于这也是 Arc 的源代码,我试图在简单性和效率之间取得平衡。如果效率低下,那么这些定义在数学上并不是最小的;我试过一次,他们做到了。 The definitions in arc.arc are also an experiment in another way. They are the language spec. The spec for isa isn't prose, like function specs in Common Lisp. This is the spec for isa: arc.arc 中的定义也是另一种方式的实验。它们是语言规范。isa 的规范不是散文,就像 Common Lisp 中的函数规范一样。这是 isa 的规范: (def isa (x y) (is (type x) y)) (def isa (x y) (is (type x) y)) It may sound rather dubious to say that the only spec for something is its implementation. It sounds like the sort of thing one might say about C++, or the Common Lisp loop macro. But that's also how math works. If the implementation is sufficiently abstract, it starts to be a good idea to make specification and implementation identical. 说某事的唯一规范是它的实现,这听起来可能相当可疑。这听起来像是人们可能会说的关于C++或Common Lisp循环宏的事情。但这也是数学的运作方式。如果实现足够抽象,那么使规范和实现相同开始是一个好主意。 I agree with Abelson and Sussman that programs should be written primarily for people to read rather than machines to execute. The Lisp defined as a model of computation in McCarthy's original paper was. It seems worth trying to preserve this as you grow Lisp into a language for everyday use. 我同意 Abelson 和 Sussman 的观点,即程序应该主要供人们阅读,而不是机器执行。在McCarthy的原始论文中,Lisp被定义为计算模型。当你把Lisp发展成一种日常使用的语言时,似乎值得尝试保留这一点。Notes 笔记 [1] Note to Lisp hackers: If you're used to the conventional Lisp cond operator, this if amounts to the same thing, but with fewer parentheses. E.g. [1] 给 Lisp 黑客的注意事项:如果你习惯了传统的 Lisp cond 运算符,那么这 if 相当于同样的事情,但括号更少。例如: (cond (a b)(c d)(t e)) (cond (a) b) (c d) (t e)) becomes 成为 (if a bc de) (如果 A、B、C、D、E) JMC's original cond didn't have implicit progn, so the parens around each pair of clauses were unnecessary. They became necessary soon after, however, when cond started to have implicit progn in the first Lisp implementations. This probably prevented people from realizing they hadn't originally been needed. But most conds in the wild seem to occur in purely functional code, and thus pay the cost in parens of implicit progn without actually needing it. My experience so far suggests it's a net win to offer progn a la carte instead of combining it with the default conditional operator. Having to use explicit dos may even be an advantage, because it calls attention to nonfunctional code. JMC 的原始 cond 没有隐式 progn,因此每对子句周围的 parens 是不必要的。然而,不久之后,当 cond 开始在第一个 Lisp 实现中具有隐式 progn 时,它们就变得必要了。这可能阻止了人们意识到他们最初并不需要。但是,大多数 conds 似乎都发生在纯函数式代码中,因此在隐式 progn 的 parens 中付出了代价,而实际上并不需要它。到目前为止,我的经验表明,提供点菜项目而不是将其与默认条件运算符结合使用是一种净胜利。必须使用显式 dos 甚至可能是一个优势,因为它会引起人们对非功能性代码的注意。 [2] "Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I," CACM, April 1960. [2] “Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I”,CACM,1960 年 4 月。 http://www-formal.stanford.edu/jmc/recursive/recursive.html