Почему аргументы для замещающих функций не оцениваются лениво?

Рассмотрим следующую простую функцию:

f <- function(x, value){print(x);print(substitute(value))}

Аргумент x в конечном итоге будет оцениваться с помощью print, но value никогда не будет. Поэтому мы можем получить такие результаты:

> f(a, a)  
Error in print(x) : object 'a' not found  
> f(3, a)  
[1] 3  
a  
> f(1+1, 1+1)  
[1] 2  
1 + 1  
> f(1+1, 1+"one")  
[1] 2  
1 + "one"

Все, что ожидалось.

Теперь рассмотрим одно и то же тело функции в функции замены:

'g<-' <- function(x, value){print(x);print(substitute(value))}

(одиночные кавычки должны быть причудливыми кавычками)

Попробуйте:

> x <- 3  
> g(x) <- 4  
[1] 3  
[1] 4  

Ничего необычного до сих пор...

> g(x) <- a  
Error: object 'a' not found  

Это неожиданно. Имя a должно быть напечатано как объект языка.

> g(x) <- 1+1  
[1] 4  
1 + 1  

Это нормально, поскольку x прежнее значение 4. Обратите внимание, что выражение не оценено.

Окончательный тест:

> g(x) <- 1+"one"  
Error in 1 + "one" : non-numeric argument to binary operator  

Подождите минуту... Почему он попытался оценить это выражение?

Хорошо, вопрос: ошибка или функция? Что здесь происходит? Я надеюсь, что некоторые пользователи гуру прольют некоторый свет о promises и ленивую оценку на R. Или мы можем просто сделать это ошибкой.

Ответ 1

Я думаю, что ключ может быть найден в этом комментарии, начиная с строка 1682 из "eval.c" (и сразу же после этого оценивается присвоение RHS):

/* It important that the rhs get evaluated first because
assignment is right associative i.e. a <- b <- c is parsed as
a <- (b <- c). */

PROTECT(saverhs = rhs = eval(CADR(args), rho));

Мы ожидаем, что если g(x) <- a <- b <- 4 + 5, как a, так и b будет присвоено значение 9; это на самом деле то, что происходит.

По-видимому, способ, которым R обеспечивает это последовательное поведение, заключается в том, чтобы всегда оценивать RHS назначения сначала, прежде чем выполнять оставшуюся часть задания. Если эта оценка не удалась (например, когда вы пытаетесь что-то вроде g(x) <- 1 + "a"), возникает ошибка и не происходит присвоение.

Ответ 2

Мы можем свести проблему к чуть более простому примеру:

g <- function(x, value)
'g<-' <- function(x, value) x
x <- 3

# Works
g(x, a)
`g<-`(x, a)

# Fails
g(x) <- a

Это говорит о том, что R делает что-то особенное при оценке функции замены: я подозреваю, что он оценивает все аргументы. Я не уверен, почему, но комментарии в коде C (https://github.com/wch/r-source/blob/trunk/src/main/eval.c#L1656 и https://github.com/wch/r-source/blob/trunk/src/main/eval.c#L1181), можно предположить, что другие промежуточные переменные не были случайно изменены.

Luke Tierney имеет длинный комментарий о недостатках текущего подхода и иллюстрирует некоторые из более сложных способов использования функций замены:

Здесь есть два вопроса:

Комплексное задание в сложном присвоении, например f(x, y[] <- 1) <- 3, может вызвать временное значение переменная для внешнего присвоения должна быть перезаписана и затем удаляется внутренним. Это можно было бы решить используя несколько временных рядов или используя обещание для этого как это сделано для RHS. Печать тогда может потребоваться вызов функции замены в сообщениях об ошибках для регулировки.

С присвоением формы f(g(x, z), y) <- w значение из z будет вычислен дважды, один раз для вызова g(x, z)и один раз для вызова функции замены g<-. Это можно было бы решить эту проблему, используя promises. Использование большего количества временных файлов не будет работать, поскольку это испортит функции замены, которые используют замену и/или нестандартная оценка (и есть пакеты, которые делают что - igraph является одним).

Ответ 3

Я собираюсь выйти на конечность здесь, поэтому, пожалуйста, люди с большим количеством знаний не стесняйтесь комментировать/редактировать.

Обратите внимание, что при запуске

'g<-' <- function(x, value){print(x);print(substitute(value))}
x <- 1
g(x) <- 5

побочный эффект заключается в том, что 5 присваивается x. Следовательно, оба должны быть оценены. Но если вы запустите

'g<-'(x,10)

выводятся значения x и 10, но значение x остается неизменным.

Спекуляция:

Таким образом, синтаксический анализатор различает, вызываете ли вы g<- во время выполнения фактического назначения, и когда вы просто вызываете g<- напрямую.