在R编程语言中,某些函数可以返回一个值,或者如果对它们进行了赋值,则可以设置。在下面的示例中,我们创建一个命名列表,并使用names()函数获取这些名称的向量:
> ll <- list(x = 1, y = 2, z = "whatever") # create a list
> names(ll)
[1] "x" "y" "z"
但是我可以用一种非常有趣的方式使用相同的函数来设置这些名称。我分配一个新载体,使其形式完全相同:
> names(ll) <- c("a", "b", "c")
> names(ll)
[1] "a" "b" "c"
这里有某种古怪的R魔术吗?还是这是一种可以用其他(深奥的)语言看到的计算机科学技术?我对DSL感兴趣,这个想法似乎很强大,我想进一步研究它。就像您在说“将功能的输入交给我,这样输出就是这个”。
这行不通,但可以想象得到:
> f <- function(x) x + 1
> f(2)
[1] 3
> z <- 3
> f(z) <- 2
Error in f(z) <- 2 : could not find function "f<-"
> z
[1] 3
我希望z等于1,因为f(1)是2。
这个想法与数学中的逆函数的概念紧密相关。当然,并非所有函数都具有逆函数,但是由于编程通常具有数学基础,所以我想知道是否可以在任何其他编程语言中进一步探讨该概念。
答案 0 :(得分:2)
我绝不是R语法方面的专家,但是来自Java / OOP背景,我可以通过以下内容解释您的观点:
> ll <- list(x = 1, y = 2, z = "whatever") # create a list
> names(ll) # call the getter for list names
> names(ll) <- c("a", "b", "c") # call the setter for list names
> f <- function(x) x + 1 # define a function
> f(2) # call the function
[1] 3
> f(3) <- 2 # makes no sense
也就是说,当names(object)
本身或在表达式的RHS上出现时,R会调用getter作为对象的名称。当它出现在分配的LHS上时,R将使用RHS上的值调用设置器。
尝试为函数调用的结果分配值没有意义。函数通常是无状态的,因此除了看到的东西之外,我们不要期望其他任何东西。
答案 1 :(得分:2)
您的第一个示例更多地是关于C / C ++中 lvalues 的功能,对于Common Lisp,则是 places 。 Generalized references基于宏扩展,可以由程序员进行扩展。
假设您构建了一个cons单元格(cons 0 1)
,并且说它绑定到了一个名为x
的局部变量。缺点单元只是一个带有两个插槽的小型结构,带有访问器car
和cdr
。例如,(car x)
是0,而(cdr x)
是1。通常,列表是通过链接cons-cell来构建的,其中cdr
是子列表。
更改插槽的历史方法是调用RPLACA/RPLACD
函数(替换汽车,替换cdr )。 SETF扩展机制是一种谈论地方及其影响方式的方式。对于cons单元,您有两个writer函数,其名称分别为(setf car)
和(setf cdr)
。名称实际上是两个元素的列表(这是函数名称不是符号的唯一情况)。
然后,您可以编写(setf (car x) 2)
来对x
进行突变,使其保持值为2。这是通过setf-expansion扩展为对RPLACA
的调用。
其他宏建立在setf
之上,并且通常以-f
后缀来命名,例如incf
:
(incf (cdr x))
以上增加X的CDR的值。
setf
还可轻松用于设置局部变量。
有趣的是,该机制可以组成;哈希表的访问者为(gethash <key> <table> &optional <default-value>)
;数组的访问者是(aref <array> ... <subscripts>)
。您可以这样写:
(setf (aref (gethash key table) index)
new-value)
以上内容将更改与index
中的key
关联的数组中位置table
的值。
合成是有效的,因为扩展只遍历嵌套的数据结构,直到要修改结构的程度;例如,如果您将一棵树tree
变异为等于
(root-node (node-a 0 1) (node-b 2 3))
然后,值2是根节点的第二个子节点的第一个子节点,根据列表位置,其写法如下:
(second (third tree))
=> 0
如果要增加该值,请输入:
(incf (second (third tree)))
INCF
非常聪明,只需要遍历列表一次;这是宏扩展的结果:
(LET* ((#:LIST (CDR (THIRD TREE))) (#:NEW (+ 1 (CAR #:LIST))))
(SB-KERNEL:%RPLACA #:LIST #:NEW))
可以通过调用define-setf-expander
来扩展此机制;例如,Cells
库实现了一种约束传播机制,例如电子表格公式(数据流,反应式编程),其中对象插槽值的更改向下传播给该插槽值的用户({{3} }。但是对于用户而言,只需调用(setf (slot object) value)
,它就可以提取底层的魔术。
众所周知,Prolog以及更一般的约束编程都允许在多个方向上调用关系(例如,使用http://stefano.dissegna.me/cells-tutorial.html解释程序):
lib(fd).
f(X,R) :- R #= X + 1.
其中X
是地面而R
左边是变量的情况:
[eclipse 3]: f(3,R).
R = 4
Yes (0.00s cpu)
其中X
可变且R
接地的情况:
[eclipse 4]: f(X,4).
X = 3
Yes (0.00s cpu)
两个都是左变量的情况:
[eclipse 5]: f(X,Y).
X = X{[-10000000 .. 9999999]}
Y = Y{[-9999999 .. 10000000]}
Delayed goals:
-1 - X{[-10000000 .. 9999999]} + Y{[-9999999 .. 10000000]} #= 0
两个都接地的情况:
[eclipse 6]: f(5,10).
No (0.00s cpu)