Как вы скрываете и пропускаете переменные в дочерних документах для сновидений?

knitr делает PDF файлы вне кода, который является комбинацией (в моем случае) R и LaTEX. Можно собрать документ из дочерних документов.

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

Есть ли способ сделать R-переменные "локальными" для дочернего документа? Как сделать вывод об экспорте переменных явным?

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

Ответ 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:

main.pdf