表示 nullary 数据构造函数的惯用方式
Idiomatic way to represent nullary data constructor
在 Haskell 中,如果我想要二叉树之类的东西,我会使用代数数据类型。
data BinTree a b = EmptyBinTree | BinTree a (Maybe b) (BinTree a) (BinTree a)
在 Common Lisp 中,我可能将一棵空树表示为专用的自评估符号,如 :empty-bin-tree
或通用符号,如 nil
。我会将二叉树的一般情况表示为
(defun make-bin-tree
(key-value
&optional (left-child :empty-bin-tree)
&optional (right-child :empty-bin-tree))
"(list key value?) left-child? right-child? -> bin-tree"
(list key-value left-child right-child))
Haskell 代码中没有明确对应于 BinTree
构造函数。
是否有一种惯用的或传统的方式来将 nullary 数据构造函数的等价物表示为当前包中的自评估符号,而不是重新使用关键字?
让一个符号自我评估是很容易的。你可以只使用 defconstant。然后你可以像你建议的那样创建一个树函数,其中左右子树是可选的,默认为空树:
(defconstant empty-tree 'empty-tree)
(defun tree (element &optional
(left empty-tree)
(right empty-tree))
(list element left right))
CL-USER> (tree 3)
(3 EMPTY-TREE EMPTY-TREE)
CL-USER> (tree 3 (tree 4))
(3 (4 EMPTY-TREE EMPTY-TREE) EMPTY-TREE)
CL-USER> (tree 3 (tree 4) (tree 5))
(3 (4 EMPTY-TREE EMPTY-TREE) (5 EMPTY-TREE EMPTY-TREE))
也就是说,我不知道这是否会被认为是特别惯用的。您现在有一种检查空树的方法,但没有确定的方法来检查非空树。也就是说,您如何知道它是一棵树还是一些列表?
我认为使用完全隐式类型或使用完全显式类型会更典型。隐式输入会说这样的话:
二叉树是:
- 形式列表(元素左子树右子树)
- 空树,无。
这很容易实现:
(defun tree-element (tree)
(first tree))
(defun tree-left (tree)
(second tree))
(defun tree-right (tree)
(third tree))
(defun treep (object)
(and (listp object)
(or (= 3 (length object)) ; doesn't check subtrees, though
(null object))))
请注意,treep 谓词不检查子树。这有点像 listp,它会检查某些东西是 nil 还是 cons,但不会不要再往下走。树的长度是否真的需要为三也是一个品味问题。毕竟,您仍然可以执行 (tree-right '(1 (2))) 并返回 nil。
您还可以做一些更明确的事情,将类型信息实际嵌入到对象中,以便您可以检查参数等。我认为这是一种不太常见的方法,但您可以使用 CLOS 或结构。默认情况下,结构也会为您提供许多相同的访问器。使用结构的方法可能如下所示(请务必阅读代码中的注释):
(defstruct (binary-tree
;; By default, DEFSTRUCT would define a BINARY-TREE-P
;; predicate that would be true only of the structure
;; objects. The structure objects are just the internal
;; nodes; NIL is the leaf, so instead we define
;; INTERNAL-BINARY-TREE-P, and define BINARY-TREE-P
;; later.
(:predicate internal-binary-tree-p))
element ; defines binary-tree-element
left ; '' binary-tree-left
right) ; '' binary-tree-right
(defun binary-tree-p (object)
(or (null object)
(internal-binary-tree-p object)))
或者,您也可以使用具有结构的类型层次结构。当然,类型层次结构的问题是您无法锁定它们。例如,您可以定义一个没有槽的二叉树 class,然后子 class 是空二叉树和内部二叉树,但是如果有人来定义另一个子 class,它也是一个二叉树,即使你想要代数行为。
Common Lisp 是一种足够灵活的语言,您可以根据需要开发代数数据类型,并且足够宽容,您可以随心所欲地强制执行。我认为最常见的方法,当然也是早期最简单的方法,就是简单地以这种方式处理您的数据。编译器不会强制您不将非树传递给期望树的函数,但您仍然可以获得大部分好处。
您可以自己滚动,如其他答案中所述。看起来您想在 Common Lisp 中使用 ML 风格的代数数据类型。事实证明你可以:tarballs_are_good 这个令人惊叹的库:https://github.com/stylewarning/cl-algebraic-data-type 实现了它们。
不幸的是,这个库不支持参数递归类型,因为仅通过宏观学很难将它们改造成动态语言。如果这种抽象对你来说是不可协商的,你可能想看看像 Shen 这样从头开始支持它的 Lisp。
编辑:trivia 现在是模式匹配库的实际标准。我相信它与 optima 兼容,但在更积极的开发中。
在 Haskell 中,如果我想要二叉树之类的东西,我会使用代数数据类型。
data BinTree a b = EmptyBinTree | BinTree a (Maybe b) (BinTree a) (BinTree a)
在 Common Lisp 中,我可能将一棵空树表示为专用的自评估符号,如 :empty-bin-tree
或通用符号,如 nil
。我会将二叉树的一般情况表示为
(defun make-bin-tree
(key-value
&optional (left-child :empty-bin-tree)
&optional (right-child :empty-bin-tree))
"(list key value?) left-child? right-child? -> bin-tree"
(list key-value left-child right-child))
Haskell 代码中没有明确对应于 BinTree
构造函数。
是否有一种惯用的或传统的方式来将 nullary 数据构造函数的等价物表示为当前包中的自评估符号,而不是重新使用关键字?
让一个符号自我评估是很容易的。你可以只使用 defconstant。然后你可以像你建议的那样创建一个树函数,其中左右子树是可选的,默认为空树:
(defconstant empty-tree 'empty-tree)
(defun tree (element &optional
(left empty-tree)
(right empty-tree))
(list element left right))
CL-USER> (tree 3)
(3 EMPTY-TREE EMPTY-TREE)
CL-USER> (tree 3 (tree 4))
(3 (4 EMPTY-TREE EMPTY-TREE) EMPTY-TREE)
CL-USER> (tree 3 (tree 4) (tree 5))
(3 (4 EMPTY-TREE EMPTY-TREE) (5 EMPTY-TREE EMPTY-TREE))
也就是说,我不知道这是否会被认为是特别惯用的。您现在有一种检查空树的方法,但没有确定的方法来检查非空树。也就是说,您如何知道它是一棵树还是一些列表?
我认为使用完全隐式类型或使用完全显式类型会更典型。隐式输入会说这样的话:
二叉树是:
- 形式列表(元素左子树右子树)
- 空树,无。
这很容易实现:
(defun tree-element (tree)
(first tree))
(defun tree-left (tree)
(second tree))
(defun tree-right (tree)
(third tree))
(defun treep (object)
(and (listp object)
(or (= 3 (length object)) ; doesn't check subtrees, though
(null object))))
请注意,treep 谓词不检查子树。这有点像 listp,它会检查某些东西是 nil 还是 cons,但不会不要再往下走。树的长度是否真的需要为三也是一个品味问题。毕竟,您仍然可以执行 (tree-right '(1 (2))) 并返回 nil。
您还可以做一些更明确的事情,将类型信息实际嵌入到对象中,以便您可以检查参数等。我认为这是一种不太常见的方法,但您可以使用 CLOS 或结构。默认情况下,结构也会为您提供许多相同的访问器。使用结构的方法可能如下所示(请务必阅读代码中的注释):
(defstruct (binary-tree
;; By default, DEFSTRUCT would define a BINARY-TREE-P
;; predicate that would be true only of the structure
;; objects. The structure objects are just the internal
;; nodes; NIL is the leaf, so instead we define
;; INTERNAL-BINARY-TREE-P, and define BINARY-TREE-P
;; later.
(:predicate internal-binary-tree-p))
element ; defines binary-tree-element
left ; '' binary-tree-left
right) ; '' binary-tree-right
(defun binary-tree-p (object)
(or (null object)
(internal-binary-tree-p object)))
或者,您也可以使用具有结构的类型层次结构。当然,类型层次结构的问题是您无法锁定它们。例如,您可以定义一个没有槽的二叉树 class,然后子 class 是空二叉树和内部二叉树,但是如果有人来定义另一个子 class,它也是一个二叉树,即使你想要代数行为。
Common Lisp 是一种足够灵活的语言,您可以根据需要开发代数数据类型,并且足够宽容,您可以随心所欲地强制执行。我认为最常见的方法,当然也是早期最简单的方法,就是简单地以这种方式处理您的数据。编译器不会强制您不将非树传递给期望树的函数,但您仍然可以获得大部分好处。
您可以自己滚动,如其他答案中所述。看起来您想在 Common Lisp 中使用 ML 风格的代数数据类型。事实证明你可以:tarballs_are_good 这个令人惊叹的库:https://github.com/stylewarning/cl-algebraic-data-type 实现了它们。
不幸的是,这个库不支持参数递归类型,因为仅通过宏观学很难将它们改造成动态语言。如果这种抽象对你来说是不可协商的,你可能想看看像 Shen 这样从头开始支持它的 Lisp。
编辑:trivia 现在是模式匹配库的实际标准。我相信它与 optima 兼容,但在更积极的开发中。