`match.call ()` и `sys.call ()` вызываются из функции окружающей среды

match.call() и sys.call() легко получить вызов текущей выполняемой функции, однако я не могу надежно получить вызов функции на один уровень выше.

Я хотел бы построить следующую фабрику функций

factory <- function(){

  CALL <- function(){
    # does operations on what would be the output of match.call() and sys.call() 
    # if they were executed in the manufactured function
  }

  CALL2 <- function() {
    # calls CALL() and does other operations
  }

  function(x, y){
    # calls CALL() and CALL2(), not necessarily at the top level
  }
}

Вот упрощенный пример с ожидаемым результатом, где я просто пытаюсь напечатать правильные match.call() и sys.call():

код

Я ожидаю, что ваш ответ изменит следующее, добавив код с # INSERT SOME CODE комментариями.

Мой код в конце вызывает функции CALL и CALL2 по-разному, чтобы проверить надежность решения.

Ожидается, что каждый из этих способов будет печатать один и тот же результат, что и {print(match.call()); print(sys.call())}.

factory <- function(){
  CALL <- function(){
    # INSERT SOME CODE HERE
  }
  CALL2 <- function() {
    # INSERT SOME CODE HERE IF NECESSARY
    CALL()
  }

  function(x, y){
    # INSERT SOME CODE HERE IF NECESSARY

    # Don't edit following code
    message("call from top level")
    CALL()
    message("call from lst")
    dplyr::lst(CALL())
    message("call from lapply")
    lapply(CALL(), identity)
    message("call from sub function")
    f <- function() CALL()
    f()
    message("call from another function from enclosing env")
    CALL2()
    message("call from lst")
    dplyr::lst(CALL2())
    message("call from lapply")
    lapply(CALL2(), identity)
    message("call from sub function")
    g <- function() CALL2()
    g()
    invisible(NULL)
  }
}

ввод

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

fun <- factory()
fun("foo", y = "bar")

OR

fun2 <- function(){
  fun("foo", y = "bar")
}
fun2()

Таким образом, решение тестируется с двумя разными стеками вызовов, опять же на надежность.

желаемый результат

Каждый раз, когда CALL вызывается в приведенном выше примере, должно выводиться следующее, независимо от его вызова:

fun(x = "foo", y = "bar")
fun("foo", y = "bar")

Это означает, что полный вывод при запуске fun("foo", y = "bar") или fun2() должен быть:

call from top level
fun(x = "foo", y = "bar")
fun("foo", y = "bar")
call from lst
fun(x = "foo", y = "bar")
fun("foo", y = "bar")
call from lapply
fun(x = "foo", y = "bar")
fun("foo", y = "bar")
call from sub function
fun(x = "foo", y = "bar")
fun("foo", y = "bar")
call from another function from enclosing env
fun(x = "foo", y = "bar")
fun("foo", y = "bar")
call from lst
fun(x = "foo", y = "bar")
fun("foo", y = "bar")
call from lapply
fun(x = "foo", y = "bar")
fun("foo", y = "bar")
call from sub function
fun(x = "foo", y = "bar")
fun("foo", y = "bar")

Может, rlang / tidyeval придет на помощь?


ЧТО Я ПЫТАЛСЯ

Я считаю, что нашел способ добиться успеха с match.call().

Чтобы убедиться, что match.call() выполняется в правильной среде, я создаю привязку ENV к среде созданной мной функции с помощью ENV <- environment(). Затем я могу получить эту среду, вызвав ENV <- eval.parent(quote(ENV)) в CALL() и CALL2(), а затем могу получить правильный результат, вызвав eval(quote(match.call()), ENV).

Однако эта же стратегия не работает с sys.call().

factory <- function(){

  CALL <- function(){
    ENV <- eval.parent(quote(ENV))
    print(eval(quote(match.call()), ENV))
    print(eval(quote(sys.call()), ENV))
  }

  CALL2 <- function() {
    ENV <- eval.parent(quote(ENV))
    CALL()
  }

  function(x, y){
    ENV <- environment()
    message("call from top level")
    CALL()
    message("call from lst")
    dplyr::lst(CALL())
    message("call from lapply")
    lapply(CALL(), identity)
    message("call from sub function")
    f <- function() CALL()
    f()
    message("call from another function from enclosing env")
    CALL2()
    message("call from lst")
    dplyr::lst(CALL2())
    message("call from lapply")
    lapply(CALL2(), identity)
    message("call from sub function")
    g <- function() CALL2()
    g()
    invisible(NULL)
  }
}

Выход:

fun <- factory()
fun("foo", y = "bar")
#> call from top level
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from lst
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from lapply
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from sub function
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from another function from enclosing env
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from lst
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from lapply
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from sub function
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
fun2 <- function(){
  fun("foo", y = "bar")
}
fun2()
#> call from top level
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from lst
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from lapply
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from sub function
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from another function from enclosing env
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from lst
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from lapply
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)
#> call from sub function
#> fun(x = "foo", y = "bar")
#> eval(quote(sys.call()), ENV)

Создано 5 июня 2019 г. пакетом REPEX (v0.2.1)

Как видите, результат показывает eval(quote(sys.call()), ENV), где я хочу видеть fun("foo", y = "bar").

Вместо print(eval(quote(sys.call()), ENV)) я также пробовал print(sys.call(1)) и print(sys.call(sys.parent())), и оба иногда печатают правильные вещи, но они не надежны.


person Moody_Mudskipper    schedule 04.06.2019    source источник
comment
(Это пример Mwe, где M намеренно написана с заглавной буквы и похожа на сокращения Mega / micro? Вау, Муди, это не коротко ...)   -  person r2evans    schedule 05.06.2019
comment
Я очень старался раздеть его до минимума :). И я провел черту, чтобы отделить вопрос от того, что я пытаюсь, не могу поправиться, извините: D   -  person Moody_Mudskipper    schedule 05.06.2019
comment
Я не уверен, что понимаю вопрос, но функция R trace часто используется для отслеживания вызовов.   -  person G. Grothendieck    schedule 05.06.2019
comment
@Moody_Mudskipper Ничего страшного ... ты не хуже меня знаешь, что многостраничный вопрос не привлекает внимания ... Мне жаль, что у меня сегодня нет времени, чтобы по-настоящему погрузиться в него. Удачи!   -  person r2evans    schedule 05.06.2019
comment
вопрос можно переформулировать примерно так: как мне исправить factory_sc1, чтобы CALL ВСЕГДА печатал fun_sc1("foo", y = "bar"), вызываю ли я напрямую fun_sc1 или через другую функцию. Не знаю, как бы я использовал trace в этом случае.   -  person Moody_Mudskipper    schedule 05.06.2019


Ответы (2)


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

factory <- function(){
  matched_call <- NULL

  CALL <- function(){
    print(matched_call)
  }
  CALL2 <- function() {
    CALL()
  }

  function(x, y){
    matched_call <<- match.call()
    on.exit(matched_call <<- NULL)

    ...
  }
}
person Alexis    schedule 06.06.2019
comment
Это здорово, я, вероятно, использую это в своем коде, наконец, мне нравится, как он оставляет чистую среду производимой функции. - person Moody_Mudskipper; 07.06.2019

Я не знаю, надежно это или идиоматично, но я мог бы решить эту проблему, используя sys.call() на rlang::frame_position().

Проблема в том, что frame_position() устарел без надлежащей замены, поэтому я определил функцию frame_pos(), которая, похоже, работает одинаково в моем случае использования:

frame_pos <- function(frame) {
  pos <- which(sapply(sys.frames(), identical, frame))
  if(!length(pos)) pos <- 0
  pos
}
factory <- function(){
  CALL <- function(){
    ENV <- eval.parent(quote(ENV))
    print(eval(quote(match.call()), ENV))
    print(sys.call(rlang::frame_position(ENV)))
    print(sys.call(frame_pos(ENV)))
  }
  CALL2 <- function() {
    ENV <- eval.parent(quote(ENV))
    CALL()
  }
  function(x, y){
    ENV <- environment()
    message("call from top level")
    CALL()
    message("call from lst")
    dplyr::lst(CALL())
    message("call from lapply")
    lapply(CALL(), identity)
    message("call from sub function")
    f <- function() CALL()
    f()
    message("call from another function from enclosing env")
    CALL2()
    message("call from lst")
    dplyr::lst(CALL2())
    message("call from lapply")
    lapply(CALL2(), identity)
    message("call from sub function")
    g <- function() CALL2()
    g()
    invisible(NULL)
  }
}
fun <- factory()
fun("foo", y = "bar")
#> call from top level
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from lst
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from lapply
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from sub function
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from another function from enclosing env
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from lst
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from lapply
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from sub function
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
fun2 <- function() fun("foo", y = "bar")
fun2()
#> call from top level
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from lst
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from lapply
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from sub function
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from another function from enclosing env
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from lst
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from lapply
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
#> call from sub function
#> fun(x = "foo", y = "bar")
#> fun("foo", y = "bar")
#> fun("foo", y = "bar")
person Moody_Mudskipper    schedule 05.06.2019