0

I'm writing a function such that callers of this function can write schemas declaratively:

myschema <- Schema(
  patientID = character,
  temp = numeric,
  treated = logical,
  reason_treated = factor(levels=c('fever', 'chills', 'nausea'))
)

Later, I'd to be able to assemble dataframes using the types declared in this schema. I think the best candidate for this job is to use the metaprogramming features available in rlang:

Schema = function(...) {
  schematypes = rlang::enexprs(...)

}

However, most of the examples pertain to capturing the expression and thereafter using them as arguments to functions, rather than as functions themselves. That is, I'm finding it hard to capture the right side of the following expression:

patientID = character

and then later being able to evaluate it later as character(myvec), whenever I get myvec. The same applies to the following:

reason_treated = factor(levels=c('fever', 'chills', 'nausea'))

which I would later like to evaluate as factor(myvec, levels=c('fever', 'chills', 'nausea'))

Thanks!

daikonradish
  • 682
  • 5
  • 14

1 Answers1

0

If I understand correctly, you are effectively constructing a schema out of functions, and you want to apply those functions to some arguments when those become available. This falls under the umbrella of functional programming rather than rlang metaprogramming.

A large portion of the functionality you want is already captured by purrr::map and its "engine" as_mapper. You can employ it directly to define

Schema <- function(...) { purrr::map( list(...), purrr::as_mapper ) }

You can now employ it to build new schemas like you suggested (with minor modifications to function definitions):

myschema <- Schema(
  patientID = as.character,   # Note the correct function name
  temp = as.numeric,          # Ditto
  treated = as.logical,       # Tritto
  reason_treated = ~factor(., levels=c('fever', 'chills', 'nausea'))
)
# $patientID
# function (x, ...) 
# as.character(x = x, ...)
# <environment: base>
#
# $temp
# function (x, ...) 
# as.double(x = x, ...)
# <environment: base>
#
# $treated
# function (x, ...) 
# as.logical(x = x, ...)
# <environment: base>
# 
# $reason_treated
# function (..., .x = ..1, .y = ..2, . = ..1) 
# factor(., levels = c("fever", "chills", "nausea"))
# <bytecode: 0x00000000027a2d00>

Given your new schema, registering new patients can be done using a sister function of map that lines up arguments from two lists / vectors:

register_patient <- function(myvec) { purrr::map2( myschema, myvec, ~.x(.y) ) }
JohnDoe <- register_patient( c(1234, 100, TRUE, "fever") )    
# $patientID
# [1] "1234"
# 
# $temp
# [1] 100
# 
# $treated
# [1] TRUE
# 
# $reason_treated
# [1] fever
# Levels: fever chills nausea

Let's verify the type of each element:

purrr::map( JohnDoe, class )
# $patientID
# [1] "character"
# 
# $temp
# [1] "numeric"
# 
# $treated
# [1] "logical"
# 
# $reason_treated
# [1] "factor"
Artem Sokolov
  • 13,196
  • 4
  • 43
  • 74