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

Этот вопрос вызвал следующий вопрос: есть ли способ просмотреть специальные примитивные функции, находящиеся в стеке вызовов?

Например, создайте функцию, которая возвращает стек вызовов при выходе:

myFun <- function(obj){
  on.exit(print(sys.calls()))
  return(obj)
}

Вызов этой функции и присвоение ее результата объекту с помощью assign позволяет избежать использования специальных примитивных функций:

> assign("myObj",myFun(4))
[[1]]
assign("myObj", myFun(4))

[[2]]
myFun(4)

Но с помощью оператора присваивания это исключается из стека.

> `<-`(myObj, myFun(6))
[[1]]
myFun(6)

Конечно, может быть не так уж часто хочется видеть оператор присваивания в стеке вызовов, но другие функции, такие как rep и log, также скрываются.


person BenBarnes    schedule 24.10.2012    source источник
comment
+1 за очень интересный и хорошо сформулированный вопрос. Этот связанный раздел в руководстве R Internals также интересен: я раньше не рассматривал различные мотивы использования примитивных функций. <-, log и UseMethod все являются примитивами, но по совершенно разным причинам.   -  person Josh O'Brien    schedule 25.10.2012
comment
@JoshO'Brien ДжошО'Брайен Я не думаю, что эти мотивы обязательно правильны - все они также могут быть выполнены с помощью функций .Internal   -  person hadley    schedule 25.10.2012


Ответы (2)


Я не думаю, что есть какой-либо способ получить доступ к вызовам примитивных функций через стек вызовов. Вот почему.

Когда оценивается «типичная» функция R:

  1. Предоставленные аргументы сопоставляются с формальными аргументами.
  2. Создается новое окружение (с указателем на окружающее его окружение), и ему присваиваются формальные аргументы.
  3. Тело функции оценивается во вновь созданной среде.

Цепочка окружающих сред, которая создается, когда вызовы функций вложены друг в друга, представляет собой «стек вызовов» или «стек кадров», к которому sys.calls(), sys.frames() и т.п. предоставляют некоторый доступ.

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

Для большего понимания вот как Джон Чемберс описывает оценку примитивных функций на странице 464 Программное обеспечение для анализа данных:

Вычисление вызова одной из этих функций начинается обычным образом, но когда оценщик обнаруживает, что объект функции является примитивом, а не функцией, определенной в R, он переходит к совершенно другому вычислению. Объект выглядит только как функциональный объект с формальными аргументами и вызовом функции .Primitive() со строковым аргументом. На самом деле она содержит только индекс таблицы, которая является частью кода C, реализующего ядро ​​R. Запись в таблице идентифицирует подпрограмму C в ядре, которая отвечает за оценку вызовов этого конкретного примитива. Оценщик передаст управление этой подпрограмме и ожидает, что подпрограмма вернет указатель языка C на объект R, представляющий значение вызова.

person Josh O'Brien    schedule 24.10.2012
comment
Также смотрите комментарии вверху src/main/eval.c - person hadley; 25.10.2012
comment
@hadley - это интересно, но все еще немного выше моего понимания. Было бы полезно, если бы я знал, как обсуждаемые здесь вызовы BUILTIN соотносятся с примитивными функциями "builtin" и "special". Люк Тирни говорит, что контекст иногда создается теперь для <- и log (например)? И что представляет собой код "foreign" в комментарии Брайана Рипли в скобках? Хм. Многому еще предстоит научиться. - person Josh O'Brien; 25.10.2012
comment
@JoshO'Brien, спасибо за отличный ответ и указатель на SoDA. Она стоит на полке на работе, но я еще не добралась до последней главы. Прочитав его и посмотрев файл eval.c, похоже, на уровне c есть механизм определения, присвоено ли объекту имя (NAMED()). Если бы можно было получить указатель на объект, созданный функцией Тайлера, можно было бы запросить в коде C, назначен ли этот объект. Мне тоже есть чему поучиться! - person BenBarnes; 25.10.2012

Я не думаю, что ответ Джоша правильный.

Что ж, было бы правильно, если бы <- было в стеке вызовов в вашем примере. Но это не так.

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

foo(bar(baz))

bar(baz) оценивается внутри foo (если вообще оценивается). Следовательно, если мы проверим стек вызовов внутри bar, вот так:

bar = function (x) {
    sys.calls()
}

… то это выглядит следующим образом:

[[1]]
foo(bar(baz))

[[2]]
bar(baz)

Увы, как вы заметили, <-=) — это не обычная функция, а примитив (BUILTINSXP). Фактически, это определено в исходном коде R< /а> следующим образом:

{"<-",      do_set,     1,  100,    -1, {PP_ASSIGN,  PREC_LEFT,   1}},

Взгляните на четвертый аргумент: 100. Комментарий перед этим кодом объясняет, что означают цифры. Вот соответствующая часть, объясняющая крайнюю левую цифру:

Z=1 говорит, что нужно оценить аргументы перед вызовом (BUILTINSXP)

Это означает, что следующий код вызова bar(baz) оценивается перед присваиванием:

`<-`(x, bar(baz))

Вот почему <- не отображается в списке sys.calls(): это не текущий вызов. Он вызывается после того, как bar завершает оценку.


Есть способ обойти это ограничение: вы можете переопределить <-/= в коде R. Если вы сделаете это, она будет вести себя как обычная функция R:

`<-` = function (lhs, rhs) {
    name = as.name(deparse(substitute(lhs), backtick = true))
    rhs # evaluate expression before passing it to `bquote`, for a cleaner call stack
    eval.parent(bquote(base::`<-`(.(name), .(rhs))))
}

Однако имейте в виду, что это повлечет за собой существенное снижение производительности для каждого последующего присваивания в области, где <- переопределено: фактически это делает присваивание примерно в 1000 раз (!!!) медленнее. Обычно это неприемлемо.

person Konrad Rudolph    schedule 02.07.2017