无损设置? [英] Nondestructive setf?

查看:131
本文介绍了无损设置?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Common Lisp似乎非常努力地提供非破坏性功能(如subst& remove)和破坏性函数,并修改常用宏(比如delete& rotatef)。大概这是为了有效支持功能性和非功能性编程风格。但是,在无处不在的 setf 的设计中似乎也有一种特殊的偏见。包含泛化引用的 setf 宏显然足够灵活,可以修改任何可指定的位置(可能除了不可变整数和字符外)。尽管存在非功能性/破坏性行为,但这种能力可能占了其广泛的用途。



问题是为什么没有相应的功能性风格无损操作符, setf (把它称为put,因为已经采用 set ),类似于其他非破坏性/破坏性的lisp操作符对。这样的操作员可能会采用相同的参数,地点和值,但会返回嵌入地点的对象的副本,而不是该地点的新值。它也可能涉及到某种通用复印机,标准 setf 在返回之前只需修改复印件。然后,非破坏性操作符可以替代 setf 用于大多数赋值,而 setf 被保留用于真正的大对象。这样的操作员是否可行(甚至可能)给出了通用复印机的(推测)要求以及是否需要从嵌入其中的任意位置恢复对象?

解决方案

无法使用通用setter,但是使用SETF时,您应该使用 DEFINE-SETF-EXPANDER 。我想你可以定义相当于镜头/功能参考一>。对于另一种方法,Clojure定义了一个更新功能(借用其他语言,我知道它存在于Prolog),这也不普遍。 FSET 库提供了一些功能结构,特别是与Clojure中的结构图相似的结合图。



假设您将自己限制为结构:

 (defstruct foo abc )
(defstruct bar xyz)

(defparameter * test *
(make-foo:a(make-bar:x 0:y 0:z 0)
:b 100
:cstring))

;; * test *现在是
;; #S(FOO:A #S(BAR:X 0:Y 0:Z 0):B 100:C字符串)

以下方法创建副本,它依赖于 copy-structure slot-value ,虽然不是标准的,但是得到很好的支持: (结构键值)
(如果(endp键)

($结构键值)

$ pre $ (复制结构))
(setf(插槽 - 值复制键)
(更新(插槽 - 复制结构))
值复制键)

值))
copy))))

您可以更新 * test * ,如下所示:

  (update * test *'(az)10)

以下是一个跟踪:

  0:(更新#S(FOO:A #S(BAR:X 0 :Y 0:Z 0):B 100:Cstring)(AZ)10)
1 :(更新#S(BAR:X 0:Y 0:Z 0)(Z)10)
2:(UPDATE 0 NIL 10)
2:UPDATE返回10
1:UPDATE返回#S(BAR:X 0:Y 0:Z 10)
0:UPDATE返回# S(FOO:A #S(BAR:X 0:Y 0:Z 10):B 100:C字符串)

为了推广,您可以接受一个函数而不是一个值,以便通过部分应用更新函数来实现等效的 incf #'1 + (结果闭包将接受列表)。



另外,您需要推广 copy 操作,这对泛型函数是可能的。同样,您可以使用其他类型的访问器,并用通用的访问函数替换 slot-value (setf访问)操作)。然而,如果你想分享一些数据,这种通用的copy / setf方法可能是浪费的。例如,您只需要复制导致您的数据的列表部分,而不是复制后续的cons单元格。



您可以定义一些用于定义自定义更新的工具:

  defmethod perform-update(new(list list)position)
(nconc(subseq list 0 position)
(list new)
(nthcdr(1+ position)list)))


Common Lisp seems to go to great lengths to provide both nondestructive functions (like subst & remove) and destructive functions and modify macros (like delete & rotatef) for general use. Presumably this is to effectively support both functional and nonfunctional styles of programming. But there also seems to be a particular bias toward the nonfunctional style in the design of the ubiquitous setf. The setf macro, incorporating generalized reference, is apparently flexible enough to modify any specifiable place (except perhaps for immutable integers and characters). This power probably accounts for its widespread useage in spite of its nonfunctional/destructive behavior.

The question is why there is not a corresponding "functional style" nondestructive operator, patterned after setf (call it put, since set is already taken), analogous to other nondestructive/destructive lisp operator pairs. Such an operator would probably take the same arguments, a place and a value, but would return a copy of the object in which the place was embedded, instead of the new value at that place. It also would probably involve a universal copier of some sort, with the standard setf simply modifying the copy before returning it. The nondestructive operator then could be used in lieu of setf for most assignments, with setf being reserved for really large objects. Is such an operator feasible (or even possible) given the (presumed) requirement for a universal copier and the need to recover an object from an arbitrary place embedded in it?

解决方案

There is no universal setter either, but with SETF you are supposed to provide one when you use 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 library provides some functional structures, notably associative maps which work like the ones in Clojure.

Suppose for a moment you limit yourself to structures:

(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")

The following approach makes a copy, it relies on copy-structure and slot-value, which, while not standard, is 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))))

You can update *test* as follows:

(update *test* '(a z) 10)

Here is a trace:

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")

In order to generalize, you could accept a function instead of a value, so that you could implement the equivalent of incf by partially applying the update function with #'1+ (the resulting closure would accept a list of keys).

Also, you need to generalize the copy operation, which is possible with generic functions. Likewise, you could use other kinds of accessors, and replace slot-value with a generic access function (for which there is a (setf access) operation). This generic copy/setf approach could be wasteful, however, if you want to share some data. For example, you only need to copy the part of a list which leads to your data, not the cons cells that are following it.

You could define some facility for defining custom updaters:

(defmethod perform-update (new (list list) position)
  (nconc (subseq list 0 position)
         (list new)
         (nthcdr (1+ position) list)))

这篇关于无损设置?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆