无损设置?
Nondestructive setf?
Common Lisp 似乎竭尽全力提供非破坏性函数(如 subst 和 remove)和破坏性函数以及修改宏(如 delete 和 rotatef)以供一般使用。据推测,这是为了有效地支持函数式和非函数式编程风格。但在无处不在的设计中似乎也特别偏向于非功能性风格 setf
。 setf
宏包含广义引用,显然足够灵活,可以修改任何可指定的位置(可能不可变的整数和字符除外)。尽管它具有 nonfunctional/destructive 行为,但这种能力可能是其广泛使用的原因。
问题是为什么没有相应的 "functional style" 非破坏性运算符,以 setf
为模式(称之为 put,因为 set
已经被采用),类似于其他 nondestructive/destructive lisp 运算符对。这样的运算符可能会采用相同的参数、位置和值,但会 return 嵌入位置的对象的副本,而不是该位置的新值。它还可能涉及某种通用复印机,标准 setf
在 return 打印之前简单地修改副本。然后可以使用非破坏性运算符代替 setf
进行大多数赋值,而 setf
保留用于非常大的对象。鉴于对通用复印机的(假定)要求以及从嵌入其中的任意位置恢复对象的需要,这样的运算符是否可行(甚至可能)?
Common Lisp 没有万能复印机,原因与它没有 CLOS objects (see, e.g., ): the power of MOP.
的内置 printable 表示相同。
具体来说,对象创建可能会产生难以保证复制的任意副作用。例如,定义 initialize-instance
for your class to modify slots based something fluid (e.g., tides or just random
). Then the result of make-instance
called with the same arguments twice may be different. You might argue that this is an argument in favor of a memcpy
风格的通用复印机。但是,现在想象一个 class 执行实例记帐(一个 :allocation :class
槽,其中包含一个哈希 table 将唯一 ID 映射到实例)。现在,从复制的对象通过 table 的往返将得到一个 不同的 对象(原始对象,而不是副本)——这是一个重大的合同违规行为。而这些例子只是冰山一角。
但是,我不同意 Common Lisp 更鼓励命令式风格而不是函数式风格。它只是不鼓励你描述的风格(copy+setf
)。这样想:从功能的 POV 来看,副本与原件没有区别(因为一切都是 immutable)。
也有 "small hints" 表现出对功能风格的明显偏好。
观察 "natural" 名称
(例如,append
)被保留
对于 非破坏性 功能;破坏性版本
(例如,nconc
)
晦涩难懂的名字。
此外,pathnames, characters and numbers (including composites like ratio
and complex
) 是
immutable,即所有路径名和数字函数创建新对象(函数式)。
因此,IMO,Common Lisp 鼓励程序员使用函数式风格,同时仍然使命令式风格可行,符合口号“简单的事情应该很容易,困难的事情应该是可能的".
也没有通用的 setter,但是当你使用 SETF 时,你应该提供一个 DEFINE-SETF-EXPANDER. I suppose you could define the equivalent of lenses/functional references. For another approach, Clojure defines an update function (borrowed from other languages, I know it exists in Prolog), which is not universal either. The FSET 库提供了一些功能结构,特别是像 Clojure 中那样工作的关联映射。
暂时假设您将自己限制在结构上:
(defstruct foo a b c)
(defstruct bar x y z)
(defparameter *test*
(make-foo :a (make-bar :x 0 :y 0 :z 0)
:b 100
:c "string"))
;; *test* is now
;; #S(FOO :A #S(BAR :X 0 :Y 0 :Z 0) :B 100 :C "string")
以下方法进行复制,它依赖于 copy-structure
和 slot-value
,虽然不标准,但为 well supported:
(defun update (structure keys value)
(if (endp keys)
value
(destructuring-bind (key &rest keys) keys
(let ((copy (copy-structure structure)))
(setf (slot-value copy key)
(update (slot-value copy key)
keys
value))
copy))))
您可以按如下方式更新*test*
:
(update *test* '(a z) 10)
这里有痕迹:
0: (UPDATE #S(FOO :A #S(BAR :X 0 :Y 0 :Z 0) :B 100 :C "string") (A Z) 10)
1: (UPDATE #S(BAR :X 0 :Y 0 :Z 0) (Z) 10)
2: (UPDATE 0 NIL 10)
2: UPDATE returned 10
1: UPDATE returned #S(BAR :X 0 :Y 0 :Z 10)
0: UPDATE returned #S(FOO :A #S(BAR :X 0 :Y 0 :Z 10) :B 100 :C "string")
为了概括,您可以接受一个函数而不是一个值,这样您就可以通过使用 #'1+
部分应用更新函数来实现 incf
的等价物(生成的闭包将接受 keys).
的列表
此外,您需要泛化 copy 操作,这可以通过泛型函数实现。同样,您可以使用其他类型的访问器,并将 slot-value
替换为通用 access
函数(其中有一个 (setf access)
操作)。但是,如果您想共享一些数据,这种通用 copy/setf 方法可能会造成浪费。例如,您只需要复制列表中指向您的数据的部分,而不需要复制它后面的 cons 单元格。
您可以定义一些工具来定义自定义更新程序:
(defmethod perform-update (new (list list) position)
(nconc (subseq list 0 position)
(list new)
(nthcdr (1+ position) list)))
Common Lisp 似乎竭尽全力提供非破坏性函数(如 subst 和 remove)和破坏性函数以及修改宏(如 delete 和 rotatef)以供一般使用。据推测,这是为了有效地支持函数式和非函数式编程风格。但在无处不在的设计中似乎也特别偏向于非功能性风格 setf
。 setf
宏包含广义引用,显然足够灵活,可以修改任何可指定的位置(可能不可变的整数和字符除外)。尽管它具有 nonfunctional/destructive 行为,但这种能力可能是其广泛使用的原因。
问题是为什么没有相应的 "functional style" 非破坏性运算符,以 setf
为模式(称之为 put,因为 set
已经被采用),类似于其他 nondestructive/destructive lisp 运算符对。这样的运算符可能会采用相同的参数、位置和值,但会 return 嵌入位置的对象的副本,而不是该位置的新值。它还可能涉及某种通用复印机,标准 setf
在 return 打印之前简单地修改副本。然后可以使用非破坏性运算符代替 setf
进行大多数赋值,而 setf
保留用于非常大的对象。鉴于对通用复印机的(假定)要求以及从嵌入其中的任意位置恢复对象的需要,这样的运算符是否可行(甚至可能)?
Common Lisp 没有万能复印机,原因与它没有 CLOS objects (see, e.g.,
具体来说,对象创建可能会产生难以保证复制的任意副作用。例如,定义 initialize-instance
for your class to modify slots based something fluid (e.g., tides or just random
). Then the result of make-instance
called with the same arguments twice may be different. You might argue that this is an argument in favor of a memcpy
风格的通用复印机。但是,现在想象一个 class 执行实例记帐(一个 :allocation :class
槽,其中包含一个哈希 table 将唯一 ID 映射到实例)。现在,从复制的对象通过 table 的往返将得到一个 不同的 对象(原始对象,而不是副本)——这是一个重大的合同违规行为。而这些例子只是冰山一角。
但是,我不同意 Common Lisp 更鼓励命令式风格而不是函数式风格。它只是不鼓励你描述的风格(copy+setf
)。这样想:从功能的 POV 来看,副本与原件没有区别(因为一切都是 immutable)。
也有 "small hints" 表现出对功能风格的明显偏好。
观察 "natural" 名称
(例如,append
)被保留
对于 非破坏性 功能;破坏性版本
(例如,nconc
)
晦涩难懂的名字。
此外,pathnames, characters and numbers (including composites like ratio
and complex
) 是
immutable,即所有路径名和数字函数创建新对象(函数式)。
因此,IMO,Common Lisp 鼓励程序员使用函数式风格,同时仍然使命令式风格可行,符合口号“简单的事情应该很容易,困难的事情应该是可能的".
也没有通用的 setter,但是当你使用 SETF 时,你应该提供一个 DEFINE-SETF-EXPANDER. I suppose you could define the equivalent of lenses/functional references. For another approach, Clojure defines an update function (borrowed from other languages, I know it exists in Prolog), which is not universal either. The FSET 库提供了一些功能结构,特别是像 Clojure 中那样工作的关联映射。
暂时假设您将自己限制在结构上:
(defstruct foo a b c)
(defstruct bar x y z)
(defparameter *test*
(make-foo :a (make-bar :x 0 :y 0 :z 0)
:b 100
:c "string"))
;; *test* is now
;; #S(FOO :A #S(BAR :X 0 :Y 0 :Z 0) :B 100 :C "string")
以下方法进行复制,它依赖于 copy-structure
和 slot-value
,虽然不标准,但为 well supported:
(defun update (structure keys value)
(if (endp keys)
value
(destructuring-bind (key &rest keys) keys
(let ((copy (copy-structure structure)))
(setf (slot-value copy key)
(update (slot-value copy key)
keys
value))
copy))))
您可以按如下方式更新*test*
:
(update *test* '(a z) 10)
这里有痕迹:
0: (UPDATE #S(FOO :A #S(BAR :X 0 :Y 0 :Z 0) :B 100 :C "string") (A Z) 10)
1: (UPDATE #S(BAR :X 0 :Y 0 :Z 0) (Z) 10)
2: (UPDATE 0 NIL 10)
2: UPDATE returned 10
1: UPDATE returned #S(BAR :X 0 :Y 0 :Z 10)
0: UPDATE returned #S(FOO :A #S(BAR :X 0 :Y 0 :Z 10) :B 100 :C "string")
为了概括,您可以接受一个函数而不是一个值,这样您就可以通过使用 #'1+
部分应用更新函数来实现 incf
的等价物(生成的闭包将接受 keys).
此外,您需要泛化 copy 操作,这可以通过泛型函数实现。同样,您可以使用其他类型的访问器,并将 slot-value
替换为通用 access
函数(其中有一个 (setf access)
操作)。但是,如果您想共享一些数据,这种通用 copy/setf 方法可能会造成浪费。例如,您只需要复制列表中指向您的数据的部分,而不需要复制它后面的 cons 单元格。
您可以定义一些工具来定义自定义更新程序:
(defmethod perform-update (new (list list) position)
(nconc (subseq list 0 position)
(list new)
(nthcdr (1+ position) list)))