Ответ 1
knitr
оценивает все куски в общей среде (возвращается knit_global()
). Это по дизайну; так же как весь код в исходном файле работает в той же среде, все куски выполняются в общей среде. То же самое относится к дочерним документам , потому что они (в принципе, не технически) просто часть основного документа, экстернализована к другому файлу.
Это не обязательно приводит к коду спагетти: ничто не мешает пользователям использовать функции и другие объекты для организации кода/данных в документах knitr
. Но, вероятно, немногие пользователи делают это...
Поэтому причиной отсутствия механизмов инкапсуляции для кусков/дочерних документов является то, что они должны совместно использовать общую среду, поскольку они являются частью одного (основного) документа.
Тем не менее, можно включить дочерние документы таким образом, чтобы пользовательский контроль над дочерними документами объектов и общим ресурсом документа. Решение основано на функции knit_child()
, которая очень похожа на параметр chunk child
. Преимущество прямого вызова knit_child()
(неявно с помощью опции child
) - это возможность установить аргумент envir
, который определяет "среду, в которой должны обрабатываться фрагменты кода" (от ?knit
).
Вокруг knit_child()
я написал обертку IsolatedChild
, чтобы упростить дело:
IsolatedChild <- function(input, ...) {
evaluationEnv <- list2env(x = list(...), parent = as.environment(2))
cat(asis_output(knit_child(input = input, envir = evaluationEnv, quiet = TRUE)))
return(evaluationEnv)
}
Аргументы, переданные в ...
, будут доступны в дочернем документе. (Назовите их, см. Пример ниже.) Функция возвращает среду, в которой был обработан дочерний документ.
Указание parent
в list2env
имеет решающее значение, и я выбрал as.environment(2)
в соответствии с этим ответом. В противном случае parent
по умолчанию будет parent.frame()
, тем самым подвергая объекты в knit_global()
дочернему документу.
assign
может использоваться для создания объектов, возвращаемых из IsolatedChild
, доступных в глобальной среде.
Обратите внимание на конструкцию cat(asis_output())
вокруг knit_child
, которая гарантирует, что вывод дочернего документа правильно включен в основной документ, независимо от параметра results
в текущем фрагменте.
Прежде чем перейти к примеру, два заключительных замечания:
- Если дочерний элемент и основной документ не должны совместно использовать какие-либо объекты, этот подход слишком сложный. Просто
knit
дочерний документ и используйте\include{}
, чтобы включить его в основной документ. - Этот подход может возникнуть с некоторыми подводными камнями. Особенно окружающая среда "изолированного ребенка" требует осторожности, потому что путь поиска может выглядеть иначе, чем ожидалось. Обратите внимание, что основной и дочерний документ разделяют параметры
knitr
. Кроме того, оба документа могут взаимодействовать через побочные эффекты (options()
,par()
, открытые устройства,...).
Ниже полного примера/демонстрации:
- Блок
inputNormal
ничего особенного не делает, это просто демонстрация нормального поведения.inputHidden
демонстрирует использованиеIsolatedChild()
, передавая две переменные дочернему документу. -
IsolatedChild()
возвращает эти два значения вместе с третьим объектом, созданным в дочернем элементе. -
check
демонстрирует, что объекты, переданные/созданные в "изолированном дочернем" объекте, не загрязняют глобальную среду. -
import
показывает, какassign
можно использовать для "импорта" объекта из "изолированного ребенка" в глобальную среду.
main.Rnw
:
\documentclass{article}
\begin{document}
<<setup>>=
library(knitr)
objInMain <- TRUE
IsolatedChild <- function(input, ...) {
evaluationEnv <- list2env(x = list(...), parent = as.environment(2))
cat(asis_output(knit_child(input = input, envir = evaluationEnv, quiet = TRUE)))
return(evaluationEnv)
}
@
<<inputNormal, child="child_normal.Rnw">>=
@
<<inputHidden, results = "asis">>=
returned <- IsolatedChild(input = "child_hidden.Rnw",
passedValue = 42,
otherPassedValue = 3.14)
cat(sprintf("Returned from hidden child: \\texttt{%s}",
paste(ls(returned), collapse = ", ")))
@
<<check, results = "asis">>=
cat(sprintf("In global evaluation environment: \\texttt{%s}",
paste(ls(), collapse = ", ")))
@
<<import, results = "asis">>=
assign("objInChildHidden", returned$objInChildHidden)
cat(sprintf("In global evaluation environment: \\texttt{%s}",
paste(ls(), collapse = ", ")))
@
\end{document}
child_normal.Rnw
:
<<inChildNormal>>=
objInChildNormal <- TRUE # visible in main.Rnw (standard behaviour)
@
child_hidden.Rnw
:
Text in \texttt{child\_hidden.Rnw}.
<<inChildHidden>>=
objInChildHidden <- TRUE
print(sprintf("In hidden child: %s",
paste(ls(), collapse = ", ")))
# Returns FALSE.
# Would be TRUE if "parent" weren't specifiet in list2env().
exists("objInMain", inherits = TRUE)
@
main.pdf
: