From 350808a8e2ca22c5c5c12e5ba2f828b3c886cd05 Mon Sep 17 00:00:00 2001 From: Tom Elliott Date: Mon, 24 Feb 2025 14:35:02 +1300 Subject: [PATCH] some docs --- R/function.R | 18 +++++++++++ R/types.R | 51 ++++++++++++++++++++++++++++-- man/ts_app.Rd | 10 ++++++ man/ts_character.Rd | 4 +++ man/ts_dataframe.Rd | 4 +++ man/ts_factor.Rd | 10 ++++++ man/ts_function.Rd | 6 ++++ man/ts_integer.Rd | 9 ++++++ man/ts_list.Rd | 4 +++ man/ts_logical.Rd | 1 + man/ts_null.Rd | 6 +++- man/ts_numeric.Rd | 10 ++++++ man/ts_object.Rd | 2 +- man/ts_union.Rd | 5 ++- man/ts_void.Rd | 3 ++ tests/testthat/test-custom-types.R | 30 ++++++++++++++++++ 16 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 tests/testthat/test-custom-types.R diff --git a/R/function.R b/R/function.R index 8e4d8e0..425a5bb 100644 --- a/R/function.R +++ b/R/function.R @@ -63,6 +63,14 @@ ts_result <- function(type, value) { #' @param result return type (ignored if overloads are provided) #' @export #' @md +#' +#' @return a ts function object which has a `call` method that will call the function with the given arguments, which will be checked for type correctness. +#' +#' @examples +#' f <- ts_function(function(x = ts_integer(1), y = ts_character(1)) { +#' x + nchar(y) +#' }, result = ts_integer(1)) +#' f$call(1, "hello") ts_function <- function(f, ..., result = ts_void()) { args <- list(...) if (!is.null(result) && !is_ts_object(result)) { @@ -117,9 +125,19 @@ print.ts_function <- function(x, ...) { #' Anything that is not a function simply returns itself. #' However, functions are wrapped with `Rserve::ocap()`, #' and the result is subsequently wrapped with `ts_app()`. +#' #' @param x A ts function object (`ts_function()`) #' @export #' @md +#' +#' @returns An object of class 'OCref', see [Rserve::ocap()] +#' +#' @examples +#' f <- ts_function(function(x = ts_integer(1), y = ts_character(1)) { +#' x + nchar(y) +#' }, result = ts_integer(1)) +#' app <- ts_app(f) # class of 'OCref' +#' # this can now be used in an Rserve application, for example ts_app <- function(x) UseMethod("ts_app") #' @export diff --git a/R/types.R b/R/types.R index 4605d54..d539ca7 100644 --- a/R/types.R +++ b/R/types.R @@ -1,4 +1,4 @@ -#' Typed object +#' Typed object (internal use only) #' #' This is the base type for all typed objects, and can be used to define #' custom types. @@ -115,9 +115,11 @@ check_type.ts_function <- function(type, x) { #' Union type #' #' Create a union of types. Currently this only accepts schemas as strings. -#' @param ... Types to merge +#' @param ... Zod types to merge (as strings) #' @export #' @md +#' @examples +#' x <- ts_union("z.number()", "z.string()") ts_union <- function(...) sprintf("z.union([%s])", paste(..., sep = ", ")) ts_array <- function(type = c("z.number()", "z.boolean()", "z.string()")) { @@ -157,6 +159,7 @@ n_type_fun <- function(n, type) { #' x$check(TRUE) #' #' \dontrun{ +#' # this will fail #' x$check(5) #' } ts_logical <- function(n = -1L) { @@ -182,6 +185,14 @@ ts_logical <- function(n = -1L) { #' @return A ts object that accepts integer scalars or vectors of length `n`. #' @export #' @md +#' @examples +#' x <- ts_integer(1) +#' x$check(1L) +#' +#' \dontrun{ +#' # this will fail +#' x$check(1:10) +#' } ts_integer <- function(n = -1L) { ts_object( n_type(n, "z.number()", "z.instanceof(Int32Array)"), @@ -209,6 +220,15 @@ ts_integer <- function(n = -1L) { #' @return A ts object that accepts numeric scalars or vectors of length `n`. #' @export #' @md +#' @examples +#' x <- ts_numeric(1) +#' x$check(1) +#' +#' \dontrun{ +#' # this will fail +#' x$check(c(1, 2, 3)) +#' x$check("a") +#' } ts_numeric <- function(n = -1L) { ts_object( n_type(n, "z.number()"), @@ -231,6 +251,9 @@ ts_numeric <- function(n = -1L) { #' @return A ts object that accepts strings or string vectors of length `n`. #' @export #' @md +#' @examples +#' x <- ts_character(1) +#' x$check("a") ts_character <- function(n = -1L) { ts_object( n_type(n, "z.string()"), @@ -256,6 +279,15 @@ vector_as_ts_array <- function(x) { #' #' @export #' @md +#' @examples +#' x <- ts_factor(levels = c("a", "b")) +#' x$check(factor("a", levels = c("a", "b"))) +#' +#' \dontrun{ +#' # this will fail +#' x$check("a") +#' x$check(factor("c", levels = c("a", "b", "c"))) +#' } ts_factor <- function(levels = NULL) { ts_object( ifelse(is.null(levels), @@ -290,6 +322,10 @@ ts_factor <- function(levels = NULL) { #' #' @export #' @md +#' +#' @examples +#' x <- ts_list(a = ts_integer(1), b = ts_character(1)) +#' x$check(list(a = 1L, b = "a")) ts_list <- function(...) { values <- list(...) @@ -348,6 +384,10 @@ ts_list <- function(...) { #' #' @export #' @md +#' +#' @examples +#' x <- ts_dataframe(a = ts_integer(1), b = ts_character(1)) +#' x$check(data.frame(a = 1L, b = "a")) ts_dataframe <- function(...) { values <- list(...) type <- "z.record(z.string(), z.any())" @@ -379,11 +419,15 @@ ts_dataframe <- function(...) { #' Null type #' -#' This is a type that only accepts `NULL`. +#' This is a type that only accepts `NULL`. For function return types, use `ts_void`. #' #' @return A ts object that only accepts `NULL`. #' @export #' +#' @md +#' @examples +#' x <- ts_null() +#' x$check(NULL) ts_null <- function() { ts_object( "z.null()", @@ -402,6 +446,7 @@ ts_null <- function() { #' @return A ts object that accepts `NULL`. #' @export #' @md +#' @seealso \code{\link{ts_null}} ts_void <- function() { ts_object( "z.void()", diff --git a/man/ts_app.Rd b/man/ts_app.Rd index 3cf8c66..9addebe 100644 --- a/man/ts_app.Rd +++ b/man/ts_app.Rd @@ -9,8 +9,18 @@ ts_app(x) \arguments{ \item{x}{A ts function object (\code{ts_function()})} } +\value{ +An object of class 'OCref', see \code{\link[Rserve:ocap]{Rserve::ocap()}} +} \description{ Anything that is not a function simply returns itself. However, functions are wrapped with \code{Rserve::ocap()}, and the result is subsequently wrapped with \code{ts_app()}. } +\examples{ +f <- ts_function(function(x = ts_integer(1), y = ts_character(1)) { + x + nchar(y) +}, result = ts_integer(1)) +app <- ts_app(f) # class of 'OCref' +# this can now be used in an Rserve application, for example +} diff --git a/man/ts_character.Rd b/man/ts_character.Rd index f18322f..425f35b 100644 --- a/man/ts_character.Rd +++ b/man/ts_character.Rd @@ -16,3 +16,7 @@ A ts object that accepts strings or string vectors of length \code{n}. Strings are represented in Zod schema as either a string (\code{z.string()}), or a string array (\code{z.array(z.string())}). } +\examples{ +x <- ts_character(1) +x$check("a") +} diff --git a/man/ts_dataframe.Rd b/man/ts_dataframe.Rd index eef97b1..d9a895a 100644 --- a/man/ts_dataframe.Rd +++ b/man/ts_dataframe.Rd @@ -15,3 +15,7 @@ A ts object that accepts data frames with the specified types. \description{ This is essentially a list, but the elements must have names and are all the same length. } +\examples{ +x <- ts_dataframe(a = ts_integer(1), b = ts_character(1)) +x$check(data.frame(a = 1L, b = "a")) +} diff --git a/man/ts_factor.Rd b/man/ts_factor.Rd index eedbf24..c18e54e 100644 --- a/man/ts_factor.Rd +++ b/man/ts_factor.Rd @@ -15,3 +15,13 @@ A ts object that accepts factors with the specified levels. \description{ Factors are integers with labels. On the JS side, these are \emph{always} represented as a string array (even if only one value - yay!). } +\examples{ +x <- ts_factor(levels = c("a", "b")) +x$check(factor("a", levels = c("a", "b"))) + +\dontrun{ +# this will fail +x$check("a") +x$check(factor("c", levels = c("a", "b", "c"))) +} +} diff --git a/man/ts_function.Rd b/man/ts_function.Rd index cfc890f..c76e353 100644 --- a/man/ts_function.Rd +++ b/man/ts_function.Rd @@ -29,3 +29,9 @@ can explicitly be defined with their types as formal arguments: \if{html}{\out{
}}\preformatted{ts_function(function(x = ts_integer(), y = ts_string()) \{ ... \}) }\if{html}{\out{
}} } +\examples{ +f <- ts_function(function(x = ts_integer(1), y = ts_character(1)) { + x + nchar(y) +}, result = ts_integer(1)) +f$call(1, "hello") +} diff --git a/man/ts_integer.Rd b/man/ts_integer.Rd index 77a5063..0c6b71d 100644 --- a/man/ts_integer.Rd +++ b/man/ts_integer.Rd @@ -16,3 +16,12 @@ A ts object that accepts integer scalars or vectors of length \code{n}. Integers are represented in Zod schema as either a number (\code{z.number()}), or a Int32Array (\code{z.instanceof(Int32Array)}). } +\examples{ +x <- ts_integer(1) +x$check(1L) + +\dontrun{ +# this will fail +x$check(1:10) +} +} diff --git a/man/ts_list.Rd b/man/ts_list.Rd index 7bfab76..7645406 100644 --- a/man/ts_list.Rd +++ b/man/ts_list.Rd @@ -15,3 +15,7 @@ A ts object that accepts lists with the specified types. \description{ A list is a vector of other robjects, which may or may not be named. } +\examples{ +x <- ts_list(a = ts_integer(1), b = ts_character(1)) +x$check(list(a = 1L, b = "a")) +} diff --git a/man/ts_logical.Rd b/man/ts_logical.Rd index 29070c6..4154abf 100644 --- a/man/ts_logical.Rd +++ b/man/ts_logical.Rd @@ -21,6 +21,7 @@ x <- ts_logical(1) x$check(TRUE) \dontrun{ +# this will fail x$check(5) } } diff --git a/man/ts_null.Rd b/man/ts_null.Rd index 26a5f67..e220ed1 100644 --- a/man/ts_null.Rd +++ b/man/ts_null.Rd @@ -10,5 +10,9 @@ ts_null() A ts object that only accepts \code{NULL}. } \description{ -This is a type that only accepts \code{NULL}. +This is a type that only accepts \code{NULL}. For function return types, use \code{ts_void}. +} +\examples{ +x <- ts_null() +x$check(NULL) } diff --git a/man/ts_numeric.Rd b/man/ts_numeric.Rd index ab18d85..005fe4b 100644 --- a/man/ts_numeric.Rd +++ b/man/ts_numeric.Rd @@ -16,3 +16,13 @@ A ts object that accepts numeric scalars or vectors of length \code{n}. Numbers are represented in Zod schema as either a number (\code{z.number()}), or a Float64Array (\code{z.instanceof(Float64Array)}). } +\examples{ +x <- ts_numeric(1) +x$check(1) + +\dontrun{ +# this will fail +x$check(c(1, 2, 3)) +x$check("a") +} +} diff --git a/man/ts_object.Rd b/man/ts_object.Rd index 10a44fb..c961da4 100644 --- a/man/ts_object.Rd +++ b/man/ts_object.Rd @@ -5,7 +5,7 @@ \alias{is_ts_object} \alias{get_type} \alias{check_type} -\title{Typed object} +\title{Typed object (internal use only)} \usage{ ts_object( input_type = "any", diff --git a/man/ts_union.Rd b/man/ts_union.Rd index 0b679ab..06614d9 100644 --- a/man/ts_union.Rd +++ b/man/ts_union.Rd @@ -7,8 +7,11 @@ ts_union(...) } \arguments{ -\item{...}{Types to merge} +\item{...}{Zod types to merge (as strings)} } \description{ Create a union of types. Currently this only accepts schemas as strings. } +\examples{ +x <- ts_union("z.number()", "z.string()") +} diff --git a/man/ts_void.Rd b/man/ts_void.Rd index 6d5d40e..4aa5d7c 100644 --- a/man/ts_void.Rd +++ b/man/ts_void.Rd @@ -13,3 +13,6 @@ A ts object that accepts \code{NULL}. This is a type that accepts null values (this would typically be used for functions that return nothing). } +\seealso{ +\code{\link{ts_null}} +} diff --git a/tests/testthat/test-custom-types.R b/tests/testthat/test-custom-types.R new file mode 100644 index 0000000..cc1d601 --- /dev/null +++ b/tests/testthat/test-custom-types.R @@ -0,0 +1,30 @@ +hist_type <- ts_object( + input_type = "z.object({ + breaks: z.array(z.number()), + counts: z.array(z.number()), + density: z.array(z.number()), + mids: z.array(z.number()), + xname: z.string(), + equidist: z.boolean(), + })", + return_type = "Robj.list({ + breaks: Robj.numeric(), + counts: Robj.numeric(), + density: Robj.numeric(), + mids: Robj.numeric(), + xname: Robj.character(1), + equidist: Robj.logical(1), + })", + check = function(x) { + stopifnot(is.list(x)) + stopifnot(all(c("breaks", "counts", "density", "mids", "xname", "equidist") %in% names(x))) + stopifnot(is.numeric(x$breaks)) + stopifnot(is.numeric(x$counts)) + stopifnot(is.numeric(x$density)) + stopifnot(is.numeric(x$mids)) + stopifnot(is.character(x$xname)) + stopifnot(is.logical(x$equidist)) + stopifnot(isTRUE(all.equal(length(x$breaks), length(x$counts), length(x$density), length(x$mids)))) + x + } +)