Beyond dataframes as parameters interfacer
is also
designed to support R package developers by providing functions for
handling missing parameters, consistency checks, and parametrising per
group analyses.
One area where a lot of boiler plate can be avoided is when a function can take multiple combinations of optional inputs that resolve to each other, and where one or more variables can be recycled.
This can require complex logic to handle missing values when some but not all of the parameters are provided and can have inconsistencies if a user provides all of the components. Similarly providing lists of different lengths to such functions can cause issues.
Suppose we are creating a function that does some mechanics:
newton2 = function(f, m = 5, a) {
# we want to ensure vectorised parameters provided are the same length
recycle(f,m,a)
# ensure parameters `f`,`m` and `a` are numeric and coerce them if not.
check_numeric(f,m,a)
# fill in missing variables using the relationships given.
resolve_missing(
f = m*a,
a = f/m,
m = f/a
)
# do something useful here...
return(tibble::tibble(f=f,m=m,a=a))
}
Inferring the acceleration, including coercion of f
:
newton2(f="10",m=2) %>% tibble::glimpse()
#> Rows: 1
#> Columns: 3
#> $ f <dbl> 10
#> $ m <dbl> 2
#> $ a <dbl> 5
Inferring the acceleration, when recycling the mass parameter:
newton2(f=1:10, m=2) %>% tibble::glimpse()
#> Rows: 10
#> Columns: 3
#> $ f <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
#> $ m <dbl> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
#> $ a <dbl> 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0
A default value for the mass is given in the function. Inferring it therefore needs the user to provide an explicit NULL value otherwise the default prevents inference and the constraints fail as follows:
try(newton2(f=1:10, a=0.5))
#> Error : inconsistent inputs detected:
#> 1) constraint 'f = m * a' is not met.
#> 2) constraint 'a = f/m' is not met.
#> 3) constraint 'm = f/a' is not met.
Combining inferring values with specified default values leads to a slightly counter-intuitive behaviour so this pattern is not recommended. It is better not to specify both a default value and constraints for mass.
newton2(f=1:10, m=NULL, a=0.5) %>% tibble::glimpse()
#> Rows: 10
#> Columns: 3
#> $ f <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
#> $ m <dbl> 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
#> $ a <dbl> 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5
Inferring the force:
newton2(m=2,a=1:10) %>% tibble::glimpse()
#> Rows: 10
#> Columns: 3
#> $ f <dbl> 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
#> $ m <dbl> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
#> $ a <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
If insufficient information is provided to calculate the remaining variables an error is thrown:
try(newton2(m=2))
#> Error : unable to infer missing variable(s): `f`, `a` using:
#> `f = m * a`
#> `a = f/m`
#> `m = f/a`
#> given known variable(s): `m` in `newton2(m = 2)`
If a default value is given then that is used in the inference if it is not specified and recycled as needed, but I still think this has the potential to be confusing: