Фабрика функций языка R: Как обеспечить безопасность? - PullRequest
1 голос
/ 01 августа 2020

Проблема

Я хотел бы проверить, является ли фабрика функций в R «безопасной». Здесь «безопасный» означает, что результаты функций, созданных фабрикой, зависят только от их аргументов, а не от глобальных переменных.

Описание

Это небезопасная фабрика:

funfac_bad = function(){  
  newfun = function()
    return(foo)
  return(newfun)
}

Возвращаемое значение newfun будет зависеть от значения foo во время выполнения newfun. Это может быть даже из-за ошибки, если foo окажется undefined.

Теперь - совершенно очевидно - эту фабрику можно сделать безопасной, привязав foo к значению внутри фабрики

funfac_good = function(){
  foo = 4711
  newfun = function()
    return(foo)
  return(newfun)
}

Я подумал Я мог проверить безопасность, проверив глобальные переменные на заводе. И действительно:

> codetools::findGlobals(funfac_bad) 
[1] "{"      "="      "foo"    "return"
> codetools::findGlobals(funfac_good)
[1] "{"      "="      "return"

Но мой реальный вариант использования (намного) сложнее. Функции фабрики зависят от подфункций и переменных с сотнями строк кода. Следовательно, я получил определение, и мои фабрики в принципе выглядят так:

funfac_my = function(){
  sys.source("file_foo.R", envir = environment())
  newfun = function()
    return(foo)
  return(newfun)
}

Это безопасная фабрика тогда и только тогда, когда код, выполняемый в «file_foo.R», связывает имя «foo» со значением. Но (вполне логично) codetools::findGlobals всегда будет сообщать "foo" как глобальную переменную.

Question

Как я могу обнаружить небезопасное поведение такой фабрики функций при получении определений?

Ответы [ 2 ]

0 голосов
/ 01 августа 2020

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

Например, предположим, что в настоящее время у вас есть

foo <- undefined_value

как единственная строка в "file_foo.R" , и вы хотите, чтобы вас предупредили об использовании undefined_value. Я предлагаю вам не делать этого. Вместо этого поместите полное определение funfac_my в "file_foo.R", заключив эту одну строку:

funfac_my = function(){
 
  foo <- undefined_value

  newfun = function()
    return(foo)
  return(newfun)
}

Теперь вы можете получить этот файл и иметь функцию funfac_my для передачи codetools::findGlobals:

codetools::findGlobals(funfac_my)
#> [1] "{"               "<-"              "="               "return"         
#> [5] "undefined_value"
0 голосов
/ 01 августа 2020

Почему бы просто не убедиться, что вы определяете значение по умолчанию для foo локально, прежде чем искать внешние файлы? Например, предположим, что у меня есть этот файл:

foo.R

foo <- "file foo"

и этот файл

bar.R

bar <- "bar"

Если я напишу свою фабрику функций следующим образом:

funfac_my <- function(my_path) {
  foo <- "fun fac foo"
  if(!missing(my_path)) sys.source(my_path, envir = environment())
  function() foo
}

Тогда я получу следующие результаты:

foo <- "global foo"

funfac_my("foo.R")()
#> [1] "file foo"

funfac_my("bar.R")()
#> [1] "fun fac foo"

funfac_my()()
#> [1] "fun fac foo"

Таким образом, вывод просто никогда не будет зависит от того, есть ли в глобальной среде объект с именем «foo» (если только сценарии, которые вы запускаете злонамеренно, не ищут глобальный объект с именем «foo» для копирования - но тогда это, вероятно, будет тем, что вы хотели, если бы все равно исходили из этого файла)

Обратите внимание, что вы можете настроить это так, чтобы выдавать ошибку вместо возврата значения по умолчанию, добавив строку if(foo == "fun fac foo") stop("object 'foo' not found") непосредственно перед последней строкой. Таким образом, это будет жаловаться, что foo не найден, даже если у вас есть неправильный объект с именем foo в глобальной рабочей области.

...