model_macro_builder: EXPERIMENTAL: Turn a function into a model macro builder A...

View source: R/BUGS_macros.R

model_macro_builderR Documentation

EXPERIMENTAL: Turn a function into a model macro builder A model macro expands one line of code in a nimbleModel into one or more new lines. This supports compact programming by defining re-usable modules. model_macro_builder takes as input a function that constructs new lines of model code from the original line of code. It returns a function suitable for internal use by nimbleModel that arranges arguments for input function. Macros are an experimental feature and are available only after setting nimbleOptions(enableModelMacros = TRUE).

Description

EXPERIMENTAL: Turn a function into a model macro builder A model macro expands one line of code in a nimbleModel into one or more new lines. This supports compact programming by defining re-usable modules. model_macro_builder takes as input a function that constructs new lines of model code from the original line of code. It returns a function suitable for internal use by nimbleModel that arranges arguments for input function. Macros are an experimental feature and are available only after setting nimbleOptions(enableModelMacros = TRUE).

Usage

model_macro_builder(fun, use3pieces = TRUE, unpackArgs = TRUE)

Arguments

fun

A function written to construct new lines of model code.

use3pieces

(TRUE or FALSE) Should the arguments from the input line be split into pieces for the LHS (left-hand side), RHS (right-hand side, possibly further split depending on unpackArgs), and stoch (TRUE if the line uses a ~, FALSE otherwise)? See details and examples.

unpackArgs

(TRUE or FALSE) Should arguments be passed as a list (FALSE) or as separate arguments (TRUE)? See details and examples.

Details

The arguments use3pieces and unpackArgs indicate how fun expects to have arguments arranged from an input line of code (processed by nimbleModel).

Consider the defaults use3pieces = TRUE and unpackArgs = TRUE, for a macro called macro1. In this case, the line of model code x ~ macro1(arg1 = z[1:10], arg2 = "hello") will be passed to fun as fun(stoch = TRUE, LHS = x, arg1 = z[1:10], arg2 = "hello").

If use3pieces = TRUE but unpackArgs = FALSE, then the RHS will be passed as is, without unpacking its arguments into separate arguments to fun. In this case, x ~ macro1(arg1 = z[1:10], arg2 = "hello") will be passed to fun as fun(stoch = TRUE, LHS = x, RHS = macro1(arg1 = z[1:10], arg2 = "hello")).

If use3pieces = FALSE and unpackArgs = FALSE, the entire line of code is passed as a single object. In this case, x ~ macro1(arg1 = z[1:10], arg2 = "hello") will be passed to fun as fun(x ~ macro1(arg1 = z[1:10], arg2 = "hello")). It is also possible in this case to pass a macro without using a ~ or <-. For example, the line macro1(arg1 = z[1:10], arg2 = "hello") will be passed to fun as fun(macro1(arg1 = z[1:10], arg2 = "hello")).

If use3pieces = FALSE and unpackArgs = TRUE, it won't make sense to anticipate a declaration using ~ or <-. Ins#' tead, arguments from an arbitrary call will be passed as separate arguments. #' For example, the line macro1(arg1 = z[1:10], arg2 = "hello") will be pa#' ssed to fun as fun(arg1 = z[1:10], arg2 = "hello").

It is extremely useful to be familiar with processing R code as an object to write fun correctly. Functions such as substitute and as.name (e.g. as.name('~')), quote, parse and deparse are particularly handy.

Multiple lines of new code should be contained in {} . Extra curly braces are not a problem. See example 2.

Macro expansion is done recursively: One macro can return code that invokes another macro.

Value

A list with a named element code that contains the replacement code.

Examples

nimbleOptions(enableModelMacros = TRUE)
nimbleOptions(verbose = FALSE)

## Example 1: Say one is tired of writing "for" loops.
## This macro will generate a "for" loop with dnorm declarations
all_dnorm <- model_macro_builder(
    function(stoch, LHS, RHSvar, start, end, sd = 1) {
        newCode <- substitute(
            for(i in START:END) {
                LHS[i] ~ dnorm(RHSvar[i], SD)
            },
            list(START = start,
                 END = end,
                 LHS = LHS,
                 RHSvar = RHSvar,
                 SD = sd))
        list(code = newCode)
    },
    use3pieces = TRUE,
    unpackArgs = TRUE 
)

model1 <- nimbleModel(
    nimbleCode(
    {
        ## Create a "for" loop of dnorm declarations by invoking the macro
        x ~ all_dnorm(mu, start = 1, end = 10)
    }
    ))

## show code from expansion of macro
model1$getCode()
## The result should be:
## {
##     for (i in 1:10) {
##         x[i] ~ dnorm(mu[i], 1)
##     }
## }

## Example 2: Say one is tired of writing priors.
## This macro will generate a set of priors in one statement
flat_normal_priors <- model_macro_builder(
    function(...) {
        allVars <- list(...)
        priorDeclarations <- lapply(allVars,
                                    function(x)
                                        substitute(VAR ~ dnorm(0, sd = 1000),
                                                   list(VAR = x)))
        newCode <- quote({})
        newCode[2:(length(allVars)+1)] <- priorDeclarations
        list(code = newCode)
    },
    use3pieces = FALSE,
    unpackArgs = TRUE
)

model2 <- nimbleModel(
    nimbleCode(
    {
        flat_normal_priors(mu, beta, gamma)
    }
    ))

## show code from expansion of macro
model2$getCode()
## The result should be:
## {
##     {
##         mu ~ dnorm(0, sd = 1000)
##         beta ~ dnorm(0, sd = 1000)
##         gamma ~ dnorm(0, sd = 1000)
##     }
## }
## Extra curly braces do not matter.

nimble documentation built on March 18, 2022, 8:03 p.m.