Какие языки программирования реализуют концепцию обратной функции, например names () в R - PullRequest
2 голосов
/ 30 марта 2019

В языке программирования 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 ]

2 голосов
/ 01 апреля 2019

Обобщенные ссылки - Common Lisp

Ваш первый пример больше о содержании lvalues ​​ в C / C ++, а в случае Common Lisp, place . Обобщенные ссылки основаны на макроразложении и могут быть расширены программистом.

Допустим, вы строите cons-ячейку (cons 0 1), и предположим, что она связана с локальной переменной с именем x. Минус ячейка - это просто небольшая структура с двумя слотами, с аксессуарами car и cdr Например, (car x) равно 0, а (cdr x) равно 1. Как правило, списки строятся с помощью объединения cons-ячеек, где cdr является подсписком.

Исторический способ изменить слоты - вызвать функции RPLACA/RPLACD ( заменить автомобиль , заменить CDR ). Механизм расширения SETF - это способ рассказать о местах и о том, как на них воздействовать. В случае cons-клеток у вас есть две функции записи, имена которых (setf car) и (setf cdr); имена - это буквально списки двух элементов (это единственный случай, когда имя функции не является символом).

Затем вы можете написать (setf (car x) 2), чтобы преобразовать x, чтобы он содержал значение 2. Это расширение макроса как вызов RPLACA с помощью setf-extension.

Другие макросы построены поверх setf и обычно имеют суффикс -f, например incf:

(incf (cdr x))

Вышеуказанное увеличивает значение CDR X. 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 реализует своего рода механизм распространения ограничений, такой как формула электронных таблиц (поток данных, реактивное программирование), где изменение значения слота объекта распространяется вниз пользователям этого значения слота ( http://stefano.dissegna.me/cells-tutorial.html). Но для пользователей это всего лишь вопрос вызова (setf (slot object) value), который абстрагирует магию, лежащую в основе.

Ограничение программирования - Пролог

Пролог и в более общем смысле программирование ограничений печально известны тем, что позволяют вызывать отношения в нескольких направлениях (например, с https://eclipseclp.org/ интерпретатором):

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)
2 голосов
/ 30 марта 2019

Я ни в коем случае не эксперт по синтаксису 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) появляется само по себе или в правой части выражения, R вызывает метод получения имен объекта. Когда это появляется в LHS назначения, R вызывает вызывающего, используя значения в RHS.

Попытка присвоить значение результату вызова функции не имеет смысла. Функция, как правило, не имеет состояния, поэтому мы не должны ожидать ничего, кроме того, что вы видели.

...