哪些编程语言实现了函数逆向的概念,例如R中的names()

时间:2019-03-30 06:33:29

标签: r functional-programming computer-science

在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。

这个想法与数学中的逆函数的概念紧密相关。当然,并非所有函数都具有逆函数,但是由于编程通常具有数学基础,所以我想知道是否可以在任何其他编程语言中进一步探讨该概念。

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)

通用参考-Common Lisp

您的第一个示例更多地是关于C / C ++中 lvalues 的功能,对于Common Lisp,则是 places Generalized references基于宏扩展,可以由程序员进行扩展。

假设您构建了一个cons单元格(cons 0 1),并且说它绑定到了一个名为x的局部变量。缺点单元只是一个带有两个插槽的小型结构,带有访问器carcdr。例如,(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

众所周知,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)