Lisp 代码的缩进
Indentation of Lisp code
我已经写了一些 Lisp 代码,并且可以运行,但我不确定如何正确缩进。
基本上我有一个全局变量和三个函数:
(setf my-hand '((3 hearts)
(5 clubs)
(2 diamonds)
(4 diamonds)
(ace spades)))
(defun rank (card)
(car card))
(defun suit (card)
(cadr card))
(defun count-suit (suit hand)
(length (remove-if-not #'(lambda (card) (equal suit (suit card))) hand)))
我可以使用全局变量和函数 rank
和 suit
,但是 count-suit
呢?我应该如何包装它的 body 并缩进它?我可以想出几种方法,但无法决定哪种方法合适。
有什么提示吗?
是否有规范的方法可以做到这一点?
我会用这个:
(defun count-suit (suit hand)
(length (remove-if-not (lambda (card)
(equal suit (suit card)))
hand)))
或者,这也可以:
(defun count-suit (suit hand)
(length
(remove-if-not (lambda (card)
(equal suit (suit card)))
hand)))
请注意 remove-if-not
只是 length
缩进的 space,因为 length
不是包含正文的形式。阅读 Riastradh's Lisp Style Rules 以获得更多指导。
Chris 的回答解决了缩进问题,但也有其他几点。首先,setf 没有声明 全局变量。为此,您需要 defvar 或 defparameter,并且您应该遵循 "earmuff" 约定:
(defparameter *my-hand*
'((3 hearts)
(5 clubs)
(2 diamonds)
(4 diamonds)
(ace spades)))
实际上,您可以使用 remove 的关键字参数来消除 count-suit 的一些缩进问题。在这种情况下,您想从手牌中移除具有 不同 花色的牌。这意味着您可以调用 remove 和 suit,对测试进行否定比较,并使用关键函数从每张牌中获取花色:
(defun count-suit (suit hand)
(length (remove suit hand
:key #'suit
:test (complement #'eql)))) ; or `:test-not #'eql`
(count-suit 'diamonds *my-hand*)
;;=> 2
但即使这样也比需要的更冗长,因为 Common Lisp 已经提供了一个 count 函数,它也有一个关键参数,所以你可以这样做:
(defun count-suit (suit hand)
(count suit hand :key #'suit))
(count-suit 'hearts *my-hand*)
;;=> 1
此外,关于访问器,您可能有兴趣使用 defstruct 来定义它们。您可以告诉 defstruct 使用列表作为其底层表示。这意味着您可以:
(defstruct (card (:type list))
rank
suit)
(make-card :rank 3 :suit 'hearts)
;;=> (3 hearts)
(card-rank '(ace spaces))
;;=> ace
(card-suit '(5 clubs))
;;=> clubs
请注意 缩进 和 格式 之间略有不同。
缩进通常表示水平移动一行的内容。通常我们已经知道线上和线上的内容。如果你要求一个典型的编辑器 indent,它只会调整行内容的水平位置。它不会跨行分发表达式。
Formatting 表示将代码布局到一行或多行。 Lisp 为自动布局提供了一个漂亮的打印机。但在编辑器中,完整布局并没有得到很好的支持,尤其是因为规则可能很复杂,而且处理注释和其他非 s 表达式代码内容有点困难。宏的布局基于简单的原则。像 LOOP 这样更复杂的宏的自动布局真的很困难。
您的问题实际上是关于格式化。
(length (remove-if-not #'(lambda (card) (equal suit (suit card))) hand))
我可以识别函数调用吗?论点是什么?什么是语法?行长呢?压痕深度?
让我们看看函数调用:把它们放在更显眼的地方:
(length
(remove-if-not
#'(lambda (card)
(equal
suit
(suit card)))
hand))
上面看起来还不错。
也许我们想专注于参数并确保两个或多个参数在不同的行上:
(length (remove-if-not #'(lambda (card)
(equal suit
(suit card)))
hand))
通常我们希望短参数列表在一行中,如果该行不是太长的话:
(length (remove-if-not #'(lambda (card)
(equal suit (suit card)))
hand))
以上是我在这种情况下要写的内容。代码结构足够清晰,不会浪费太多space.
格式化代码意味着应用大量本地和全局constraints/rules。
如果我们看一下表达式,我们也会想以不同的方式编写它,因为它会触发很多通常不喜欢的事情:
- 可以计数,但不使用计数功能
- 它做了不必要的工作
- 它创建一个 lambda 表达式来提取值并针对项目对其进行测试:提取和测试
所以:
(count suit hand :key #'suit :test #'eql)
或只是(eql
是默认值):
(count suit hand :key #'suit)
返回格式化。我们可以做一些实验,看看 Lisp 是如何做到的,因为它内置了一个代码格式化程序(这里是 Clozure Common Lisp):
? (defun test ()
(dolist (*print-right-margin* '(80 60 40 30))
(format t "~%Margin: ~a" *print-right-margin*)
(pprint '(length (remove-if-not #'(lambda (card) (equal suit (suit card))) hand)))))
TEST
? (test)
Margin: 80
(LENGTH (REMOVE-IF-NOT #'(LAMBDA (CARD) (EQUAL SUIT (SUIT CARD))) HAND))
Margin: 60
(LENGTH (REMOVE-IF-NOT
#'(LAMBDA (CARD) (EQUAL SUIT (SUIT CARD)))
HAND))
Margin: 40
(LENGTH
(REMOVE-IF-NOT
#'(LAMBDA
(CARD)
(EQUAL SUIT (SUIT CARD)))
HAND))
Margin: 30
(LENGTH
(REMOVE-IF-NOT
#'(LAMBDA
(CARD)
(EQUAL
SUIT
(SUIT CARD)))
HAND))
尽管在许多情况下手动格式化的代码可能看起来更好,但熟悉自动格式化(也称为漂亮打印或 'grinding')并能够处理它还是很有用的。
我已经写了一些 Lisp 代码,并且可以运行,但我不确定如何正确缩进。
基本上我有一个全局变量和三个函数:
(setf my-hand '((3 hearts)
(5 clubs)
(2 diamonds)
(4 diamonds)
(ace spades)))
(defun rank (card)
(car card))
(defun suit (card)
(cadr card))
(defun count-suit (suit hand)
(length (remove-if-not #'(lambda (card) (equal suit (suit card))) hand)))
我可以使用全局变量和函数 rank
和 suit
,但是 count-suit
呢?我应该如何包装它的 body 并缩进它?我可以想出几种方法,但无法决定哪种方法合适。
有什么提示吗?
是否有规范的方法可以做到这一点?
我会用这个:
(defun count-suit (suit hand)
(length (remove-if-not (lambda (card)
(equal suit (suit card)))
hand)))
或者,这也可以:
(defun count-suit (suit hand)
(length
(remove-if-not (lambda (card)
(equal suit (suit card)))
hand)))
请注意 remove-if-not
只是 length
缩进的 space,因为 length
不是包含正文的形式。阅读 Riastradh's Lisp Style Rules 以获得更多指导。
Chris 的回答解决了缩进问题,但也有其他几点。首先,setf 没有声明 全局变量。为此,您需要 defvar 或 defparameter,并且您应该遵循 "earmuff" 约定:
(defparameter *my-hand*
'((3 hearts)
(5 clubs)
(2 diamonds)
(4 diamonds)
(ace spades)))
实际上,您可以使用 remove 的关键字参数来消除 count-suit 的一些缩进问题。在这种情况下,您想从手牌中移除具有 不同 花色的牌。这意味着您可以调用 remove 和 suit,对测试进行否定比较,并使用关键函数从每张牌中获取花色:
(defun count-suit (suit hand)
(length (remove suit hand
:key #'suit
:test (complement #'eql)))) ; or `:test-not #'eql`
(count-suit 'diamonds *my-hand*)
;;=> 2
但即使这样也比需要的更冗长,因为 Common Lisp 已经提供了一个 count 函数,它也有一个关键参数,所以你可以这样做:
(defun count-suit (suit hand)
(count suit hand :key #'suit))
(count-suit 'hearts *my-hand*)
;;=> 1
此外,关于访问器,您可能有兴趣使用 defstruct 来定义它们。您可以告诉 defstruct 使用列表作为其底层表示。这意味着您可以:
(defstruct (card (:type list))
rank
suit)
(make-card :rank 3 :suit 'hearts)
;;=> (3 hearts)
(card-rank '(ace spaces))
;;=> ace
(card-suit '(5 clubs))
;;=> clubs
请注意 缩进 和 格式 之间略有不同。
缩进通常表示水平移动一行的内容。通常我们已经知道线上和线上的内容。如果你要求一个典型的编辑器 indent,它只会调整行内容的水平位置。它不会跨行分发表达式。
Formatting 表示将代码布局到一行或多行。 Lisp 为自动布局提供了一个漂亮的打印机。但在编辑器中,完整布局并没有得到很好的支持,尤其是因为规则可能很复杂,而且处理注释和其他非 s 表达式代码内容有点困难。宏的布局基于简单的原则。像 LOOP 这样更复杂的宏的自动布局真的很困难。
您的问题实际上是关于格式化。
(length (remove-if-not #'(lambda (card) (equal suit (suit card))) hand))
我可以识别函数调用吗?论点是什么?什么是语法?行长呢?压痕深度?
让我们看看函数调用:把它们放在更显眼的地方:
(length
(remove-if-not
#'(lambda (card)
(equal
suit
(suit card)))
hand))
上面看起来还不错。
也许我们想专注于参数并确保两个或多个参数在不同的行上:
(length (remove-if-not #'(lambda (card)
(equal suit
(suit card)))
hand))
通常我们希望短参数列表在一行中,如果该行不是太长的话:
(length (remove-if-not #'(lambda (card)
(equal suit (suit card)))
hand))
以上是我在这种情况下要写的内容。代码结构足够清晰,不会浪费太多space.
格式化代码意味着应用大量本地和全局constraints/rules。
如果我们看一下表达式,我们也会想以不同的方式编写它,因为它会触发很多通常不喜欢的事情:
- 可以计数,但不使用计数功能
- 它做了不必要的工作
- 它创建一个 lambda 表达式来提取值并针对项目对其进行测试:提取和测试
所以:
(count suit hand :key #'suit :test #'eql)
或只是(eql
是默认值):
(count suit hand :key #'suit)
返回格式化。我们可以做一些实验,看看 Lisp 是如何做到的,因为它内置了一个代码格式化程序(这里是 Clozure Common Lisp):
? (defun test ()
(dolist (*print-right-margin* '(80 60 40 30))
(format t "~%Margin: ~a" *print-right-margin*)
(pprint '(length (remove-if-not #'(lambda (card) (equal suit (suit card))) hand)))))
TEST
? (test)
Margin: 80
(LENGTH (REMOVE-IF-NOT #'(LAMBDA (CARD) (EQUAL SUIT (SUIT CARD))) HAND))
Margin: 60
(LENGTH (REMOVE-IF-NOT
#'(LAMBDA (CARD) (EQUAL SUIT (SUIT CARD)))
HAND))
Margin: 40
(LENGTH
(REMOVE-IF-NOT
#'(LAMBDA
(CARD)
(EQUAL SUIT (SUIT CARD)))
HAND))
Margin: 30
(LENGTH
(REMOVE-IF-NOT
#'(LAMBDA
(CARD)
(EQUAL
SUIT
(SUIT CARD)))
HAND))
尽管在许多情况下手动格式化的代码可能看起来更好,但熟悉自动格式化(也称为漂亮打印或 'grinding')并能够处理它还是很有用的。