破坏性地点修改操作符

时间:2017-02-12 18:29:21

标签: common-lisp

CLtL2参考清楚地区分了非破坏性和破坏性的普通lisp操作。但是,在破坏性阵营中,在标记简单返回结果的那些与另外修改一个地方(作为参数)以包含结果的那些之间似乎不太清楚。通常的兼并" f"这样的地方修改操作(例如,setf,incf,alexandria:deletef)有点零星,并且也适用于许多地方访问者(例如,aref,getf)。在理想的函数式编程风格中(仅基于返回值),这种混淆可能不是问题,但似乎它可能导致在一些使用场所修改的实际应用中出现编程错误。由于不同的实现可以不同地处理位置结果,因此不会影响可移植性?甚至似乎很难测试特定实现的方法。

为了更好地理解上述区别,我将破坏性的common-lisp 序列操作划分为两个类别,对应于"参数返回"和"操作返回"。有人可以为我验证或使这些类别无效吗?我假设这些类别也适用于其他类型的破坏性操作(对于列表,散列表,数组,数字等)。

参数返回:填充,替换,映射到

操作返回:delete,delete-if,delete-if-not,delete-duplicates,nsubstitute,nsubstitute-if,nsubstitute-not-if,nreverse,sort,stable-sort,merge

2 个答案:

答案 0 :(得分:1)

  

但是,在破坏性的阵营中,在标记仅仅返回结果的那些之间的区别似乎不那么明确。

尽管存在诸如n前缀之类的有用约定,但关于哪种操作具有破坏性或不具有破坏性,没有简单的句法标记。请记住,CL是受不同Lisps启发的标准,它无助于强制执行一致的术语。

  

通常的兼并" f"这样的地方修改操作(例如,setf,incf,alexandria:deletef)有点零星,并且也适用于许多地方访问者(例如,aref,getf)。

所有setf扩展器都应以f结尾,但并非以f结尾的所有内容都是setf扩展器。例如,aref的名称来自 array reference ,并且不是宏。

  

...但似乎它可能导致一些使用地点修改的实际应用程序中的编程错误。

大多数数据都是可变的(见评论);一旦你在CL中编写代码时考虑到这一点,你就要注意不要修改你自己没有创建的数据。至于在无意中使用破坏性操作代替非破坏性操作,我不知道:我想它可能会发生,sortdelete,也许是你第一次使用它们。在我看来,deleteremove更强大,更具破坏性,但可能是因为我已经知道其中的差异。

  

由于不同的实现可以不同地处理场所结果,因此不能影响可移植性吗?

如果您想要便携性,请遵循规范,这不能提供太多保证w.r.t.应用了哪些破坏性操作。以DELETE为例(强调我的):

  

序列可能被破坏并用于构建结果;但是,结果可能会或可能不会与序列相同。

假设列表的修改方式,或者即使它被修改,也是错误的。实际上,您可以在最小实现中将delete实现为remove的别名。在所有情况下,您都使用函数的返回值deleteremove都具有相同的签名)。

分类

  

我将破坏性的common-lisp序列操作划分为两个类别,对应于"参数返回"和"操作返回"。

根本不清楚这些类别应该代表什么。这些定义是你想到的吗?

  1. 参数返回操作是将其参数之一作为返回值返回,可能已修改。

  2. 操作返回操作是一个结果基于其参数之一的操作,并且可能与该参数相同,但不一定是。

  3. 操作返回的定义非常模糊,包含破坏性和非破坏性操作。我会将cons归类为因为它不会返回其中一个论点; OTOH,这是一个纯粹的功能操作。

    除了破坏性非破坏性之外,我还没有真正了解这些类别提供的内容。

    Setf组合问题

    假设您编写了一个函数(remote host key),它从远程键/值数据存储区获取值。假设您定义(setf remote)以便更新远程值。

    您可能希望(setf (first (remote host key)) value)

    1. 从主机获取列表​​,按键索引,
    2. 按值替换第一个元素
    3. 将更改推送回远程主机。
    4. 但是,步骤3通常不会发生:本地列表已就地修改(这是最有效的替代方案,但它使setf扩展在更新方面有点懒惰)。您可以定义一组新的宏,例如始终使用DEFINE-SETF-EXPANDER实现整个往返。

答案 1 :(得分:1)

让我试着通过介绍一些概念来解决你的问题。

我希望它可以帮助您巩固您的知识并找到有关此主题的剩余答案。

第一个概念是非破坏性破坏性行为。

非破坏性的功能不会改变传递给它的数据。

具有破坏性的功能可能会改变传递给它的数据。

您可以将(非)破坏性应用于除单一功能之外的其他功能。例如,如果函数存储传递给它的数据,比如在对象的插槽中,那么破坏性的 ness 取决于该对象的行为,其他操作,事件等等。

立即修改其参数的函数的约定是(通常)前缀为n

约定并没有相反的方式,有很多以n开头的函数(例如not / nullnth,{{ 1}},ninthnotanynotevery等。)还有一些值得注意的例外情况,例如numberpdeletemergesort。自然掌握它们的唯一方法是时间/经验。例如,每当您看到一个您还不知道的功能时,请始终参考HyperSpec。

此外,您通常需要存储某些破坏性函数的结果,例如stable-sortdelete,因为它们可能会选择跳过列表的头部或根本不具有破坏性。 sort实际上可能会返回空列表delete,这是无法通过修改后的利弊获得的。

第二个概念是广义参考

广义引用是任何可以保存数据的引用,例如变量,缺点的汽车和cdr,数组或散列表的元素位置,对象的插槽等。

对于每个容器数据结构,您需要知道特定的修改功能。但是,对于某些通用引用,可能没有修改它的函数,例如局部变量,在这种情况下仍然有特殊的形式来修改它。

因此,为了修改任何通用引用,您需要知道其修改形式。

与广义引用密切相关的另一个概念是地方。标识广义引用的表单称为地点。或者换句话说,一个地方是代表一般参考的书面方式(形式)。

对于每种地方,你都有一个读者形式和一个作家形式。

记录了其中一些表单,例如使用变量的符号来读取它,并nil要写入的变量,或setq / car来读取和cdr / rplaca写一个缺点。其他只记录为访问者,例如rplacd从数组中读取;它的作者形式实际上没有记录。

要获取这些表单,您需要get-setf-expansion。实际上,您还会获得一组变量及其初始化形式(将用作aref),这些形式将由读者表单和/或编写器表单使用,以及一组变量(将被绑定到新的价值观)将由作家表格使用。

如果您之前使用过Lisp,那么您可能已经使用过let*setf是一个宏,它生成在扩展范围(环境)内运行的代码。

本质上,它的行为就像使用setf一样,为变量生成get-setf-expansion形式并初始化表单,为writer变量生成额外的绑定以及value(s)形式的结果和在所有这些环境中调用编写器表单。

例如,让我们定义一个let*宏,它只占用一个地方和一个新值形式:

my-setf1

然后,您可以将(defmacro my-setf1 (place newvalue &environment env) (multiple-value-bind (vars vals store-vars writer-form reader-form) (get-setf-expansion place env) `(let* (,@(mapcar #'(lambda (var val) `(,var ,val)) vars vals)) ;; In case some vars are used only by reader-form (declare (ignorable ,@vars)) (multiple-value-bind (,@store-vars) ,newvalue ,writer-form ;; Uncomment the next line to mitigate buggy writer-forms ;;(values ,@store-vars) )))) 定义为:

my-setf

这种宏有一个约定,后缀为(defmacro my-setf (&rest pairs) `(progn ,@(loop for (place newvalue) on pairs by #'cddr collect `(my-setf1 ,place ,newvalue)))) ,例如f本身,setfpsetfshiftf,{ {1}},rotatefincfdecf

同样,惯例并没有相反的方式,有些运算符以getf结尾,例如remffaref,它们是函数,svref是条件执行特殊运算符。而且,有一些值得注意的例外,例如find-ififpushpushnewpopldb和{{1 }}

根据您的观点,更多的运营商具有隐含的破坏性,即使没有有效标记。

例如,每个定义运算符(例如宏mask-fieldassertcheck-typedefundefpackage,函数defclass )更改全局环境或临时环境,例如编译环境。

其他人,例如defgenericdefmethodload,取决于他们执行的表单。对于compile-file,它还取决于它将编译环境与启动环境隔离的程度。

其他运营商,例如compileevalcompile-filemakunboundfmakunboundinternexport,{ {1}},shadowuse-packagerename-packageadjust-arrayvector-pushvector-push-extendvector-pop,{{1 }},remhashclrhash,(或多或少)显然有副作用

而这最后一个概念可能是最广泛的。通常,副作用被视为一种环境中的任何可观察的变化。因此,不改变数据的功能通常被认为没有副作用。

然而,这是不明确的。您可能会认为所有代码执行都会产生副作用,具体取决于您定义的环境或您可以测量的内容(例如,消耗的配额,CPU时间和实时,已用内存,GC开销,资源争用,系统温度,能耗,电池消耗)。

注意:示例列表都不详尽。