Приоритет вызова функции в R

На стандартной странице справки R для приоритета оператора они не включать вызовы функций, что, на мой взгляд, кажется довольно небрежным. Это вызывало у меня некоторые проблемы, поэтому я решил просто использовать метод проб и ошибок с substitute и обнаружил, что приоритет, похоже, лежит между [[ и ^:

> substitute(a^b())[[1]]
`^`
> substitute(a[b]())[[1]]
a[b]

В инфиксной нотации это будут (^ a (b ())) и (([ a b) ()) (обозначая оператор вызова как ()). Говоря простым языком, первый пример показывает, что экспоненциальная функция вызывается для аргументов a и b(), тогда как во втором примере конечным результатом является вызов функции a[b].

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


person Jon Claus    schedule 15.05.2014    source источник


Ответы (2)


Приоритет вызова функции является постоянным

R очень похож на lisp, только внутри.

Есть SEXP, например lisp; SEXP — это кортеж (список), где первый элемент ([[1]]) кортежа является оператором, а остальные элементы (которые обычно сами являются другими SEXP) являются аргументами оператора.

Когда вы пишете

paste("a",1 + 2)

Р понимает

(`paste`,"a",(`+`, 1, 2))

Когда вы запускаете замену, вы получаете SEXP (хотя они красиво печатаются, как код R), и первым элементом (самого внешнего) SEXP является оператор last, который будет применяться в выражении - то есть самый низкий приоритет.

Как вы, наверное, знаете, вы можете просмотреть части выражения, используя что-то вроде:

> str(as.list(quote(a^b())))
List of 3
 $ : symbol ^
 $ : symbol a
 $ : language b()

Чтобы применить это понимание к приоритету в вашем примере.

Какой последний оператор a^b()?

Рассмотрим поэтапно

  1. заменить и оценить a
  2. заменить и оценить b
  3. оценить (результат шага) 2 без аргументов (это называется вызовом)
  4. заменить и оценить ^
  5. оценить 4 с аргументами 1 и 3

Таким образом, последний оператор — это значение с именем ^.

Далее, какой последний оператор a[b]()?

  1. заменить и оценить a
  2. заменить и оценить b
  3. заменить и оценить [
  4. оценить (результат шага) 3 с аргументами (результат шага) 1 и (результат шага) 2
  5. оценить (результат шага) 4

В этом случае (результат шага) 4 имеет удобное имя a[b].

Таким образом, последний оператор является вызовом (оценка без аргументов) a[b].


Изменить: предостережение

Я упростил здесь реальную ситуацию, потому что из-за особенности R, в силу которой аргументы функции передаются как невычисленные пары (окружение, выражение) функциям (операторам) (а не по ссылке или по значению), в то время как 'commit' порядок примерно такой же, как указано выше, реальный порядок отправки на самом деле обратный - или даже пропускает шаги. Однако вам пока не нужно беспокоиться об этом.

person Alex Brown    schedule 15.05.2014
comment
Убрал лишнюю правую скобку в выражении в вашем первом вопросе, но также возник соблазн убрать [[1]], поскольку на самом деле этого не было в выражении. Ты согласен? - person IRTFM; 16.05.2014

Возможно, это не проблемы «приоритета», а скорее проблемы синтаксического анализа. (Но если подумать, это кажется приоритетом и вызвано необходимостью завершения сопоставления аргументов всех аргументов между «[» и «]».) В первом случае дерево синтаксического анализа строится как:

            `^`
            /  \
           a    b

> substitute(a^b())[1]
`^`()
> substitute(a^b())[[1]]
`^`
> substitute(a^b())[[2]]
a
> substitute(a^b())[[3]]
b()

Во втором случае он был построен как

             a[b]
            /
           NULL

Но первый элемент также будет иметь структуру:

            `[`
            / \
           a   b

> substitute(a[b]())[[1]][[1]]
`[`
> substitute(a[b]())[[1]][[2]]
a
> substitute(a[b]())[[1]][[3]]
b

Я думаю, что двусмысленность может возникнуть из-за двух функций (^ и [), только последняя может фактически предоставить функцию, поэтому ее нужно будет обработать в первую очередь. Результат вычисления a^b никогда не может быть функцией, поэтому имеет смысл обрабатывать как ^(a, b() )

Когда дело доходит до того, чтобы что-то подобное действительно работало, я не думаю, что второй вариант очень полезен. Чтобы получить извлечение и замену из рабочей области, вам нужен дополнительный шаг извлечения:

b <- list(mean)
> eval( substitute(a^b(1:10) , list(a=2) ))
Error in eval(expr, envir, enclos) : could not find function "b"
> eval( substitute(a^b[[1]](1:10) , list(a=2) ))
[1] 45.25483

Следуя предложению @hadley, я скопировал его функцию ast из pryr и сопутствующую ей функцию call_tree в модуле draw_tree.r по адресу репозиторий pyr на github. Мне нужно было сделать это таким образом, так как я в дороге, а мой ноутбук все еще застрял на устаревшей версии R, в которой нет двоичного файла pyr. Также необходимо установить и загрузить pkg:stringr, чтобы получить str_c.

При этом мы видим разницу:

ast(a[b]())
\- ()
 \- ()
  \- `[
  \- `a
  \- `b 
ast(a^b())
\- ()
 \- `^
 \- `a
 \- ()
  \- `b 

Довольно ловко @hadley.

person IRTFM    schedule 15.05.2014
comment
См. pryr::ast() для автоматического рисования дерева. - person hadley; 18.05.2014