knitr
valuta tutti i blocchi in un ambiente comune (restituito da knit_global()
). Questo è di design; proprio come tutto il codice in un file sorgente viene eseguito nello stesso ambiente, tutti i blocchi vengono eseguiti in un ambiente comune. Lo stesso vale per child documents perché sono (in linea di principio, non tecnicamente) solo una parte del documento principale, esternalizzata ad un altro file.
Questo non porta necessariamente al codice spaghetti: Nulla impedisce agli utenti di utilizzare funzioni e altri oggetti per organizzare codice/dati nei documenti knitr
. Ma probabilmente pochi utenti fanno ...
Quindi la ragione per la quale non vi sono meccanismi di incapsulamento per chunks documenti/bambino è che essi sono suppone di condividere un ambiente comune in quanto sono parte di una documento (principale).
Tuttavia, lo è possibile includere documenti figlio in un modo che consenta all'utente il controllo degli oggetti documenti figlio e della condivisione documento principale. La soluzione è basata sulla funzione knit_child()
che è molto simile allo chunk optionchild
. Il vantaggio di chiamare knit_child()
direttamente (vs implicitamente tramite l'opzione child
) è la possibilità di impostare l'argomento envir
che definisce "l'ambiente in cui i pezzi di codice devono essere valutati" (da ?knit
).
Intorno knit_child()
, ho scritto l'involucro IsolatedChild
per semplificare le cose:
IsolatedChild <- function(input, ...) {
evaluationEnv <- list2env(x = list(...), parent = as.environment(2))
cat(asis_output(knit_child(input = input, envir = evaluationEnv, quiet = TRUE)))
return(evaluationEnv)
}
Argomenti passati al ...
saranno disponibili nel documento bambino. (Assegnare loro un nome, vedere l'esempio seguente). La funzione restituisce l'ambiente in cui è stato valutato il documento figlio.
Specificare parent
in list2env
è fondamentale e ho scelto as.environment(2)
secondo this answer. In caso contrario, sarebbe parent
default parent.frame()
, esponendo così gli oggetti in knit_global()
al documento bambino.
assign
può essere utilizzato per rendere gli oggetti restituiti da IsolatedChild
disponibili nell'ambiente globale.
Nota la cat(asis_output())
costruzione attorno knit_child
che assicura che l'uscita dal documento bambino è incluso correttamente nel documento principale, indipendentemente dall'impostazione results
nel chunk corrente.
Prima di passare alla esempio, due osservazioni finali:
- Se il bambino e il documento principale non sono tenuti a condividere eventuali oggetti, questo approccio è eccessivamente complesso. Semplicemente
knit
il documento figlio e utilizzare \include{}
per includerlo nel documento principale.
- Questo approccio potrebbe presentarsi con alcune insidie. Soprattutto l'ambiente che racchiude il "bambino isolato" richiede cautela perché il percorso di ricerca potrebbe apparire diverso dal previsto. Si noti che il documento principale e secondario condivide le opzioni
knitr
. Inoltre, entrambi i documenti potrebbero interagire tramite effetti collaterali (options()
, par()
, dispositivi aperti, ...).
Qui di seguito un esempio completo/demo:
- Il pezzo
inputNormal
non fa nulla di speciale, è solo una dimostrazione del comportamento normale. inputHidden
dimostra l'uso di IsolatedChild()
, passando due variabili al documento figlio.
IsolatedChild()
restituisce questi due valori insieme a un terzo oggetto creato nel figlio.
check
dimostra che gli oggetti passati/creati nel "figlio isolato" non inquinano l'ambiente globale.
import
mostra come assign
può essere utilizzato per "importare" un oggetto dal "figlio isolato" nell'ambiente globale.
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
:
Se non si vuole il documento principale e i documenti figlio per interagire, perché usi i documenti figli? Se si desidera combinare diversi file di origine (Rnw) in un documento LaTeX, è possibile 'knit' i file Rnw separatamente e quindi includerli nel documento principale tramite' \ include {} '. –
Voglio che interagiscano, ma in modo controllato.In un normale linguaggio di programmazione, si fanno funzioni con parametri e un valore di ritorno. Ciò consente chiarezza sull'accoppiamento. – dfrankow