From e9751e5c0a3380d96016286f622907fbb0d28508 Mon Sep 17 00:00:00 2001 From: Tom Elliott Date: Thu, 25 Jul 2024 21:32:08 +1200 Subject: [PATCH] some more types --- R/types.R | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/R/types.R b/R/types.R index 03002b7..7858897 100644 --- a/R/types.R +++ b/R/types.R @@ -1,3 +1,13 @@ +#' Typed object +#' +#' This is the base type for all typed objects. It is not meant to be used directly. +#' +#' @param type The type of the object that Typescript expect to send to R. +#' @param type_fn The type of the object that Typescript expects to recieve from R. +#' @param default The default value of the object. +#' @param check A function that checks the object and returns it if it is valid. This operates on the R side and is mostly for development and debugging purposes. It is up to the developer to ensure that all functions return the correct type of object always. +#' +#' @md object <- function(type = "any", type_fn = "any", default = NULL, @@ -95,3 +105,108 @@ ts_character <- function(n = -1L) { } ) } + +vector_as_ts_array <- function(x) { + paste("[\"", paste(x, collapse = "\", \""), "\"]", sep = "") +} + +#' Typed factor +#' +#' Factors are integers with labels. On the JS side, these are *always* represented as a string array (even if only one value - yay!). +#' +#' @param levels A character vector of levels (optional). +#' +#' @export +#' @md +ts_factor <- function(levels = NULL) { + object( + sprintf("(%s)[]", paste(levels, collapse = " | ")), + if (is.null(levels)) { + "Factor" + } else { + sprintf("Factor<%s>", vector_as_ts_array(levels)) + }, + check = function(x) { + if (!is.factor(x)) stop("Expected a factor") + if (!is.null(levels) && !identical(levels, levels(x))) { + stop("Expected a factor with levels ", levels) + } + x + } + ) +} + +# table? + +#' Typed list +#' +#' A list is a vector of other robjects, which may or may not be named. +#' +#' @export +#' @md +ts_list <- function(values = NULL) { + type <- "[]" + type_fn <- "" + if (!is.null(values)) { + types <- sapply(values, function(x) x$type) + type_funs <- sapply(values, function(x) x$type_fn) + if (!is.null(names(values))) { + type <- sprintf( + "{%s}", + paste(names(values), types, sep = ": ", collapse = ", ") + ) + type_fn <- sprintf( + "{%s}", + paste(names(values), type_funs, sep = ": ", collapse = ", ") + ) + } else { + type <- sprintf("[%s]", paste(values, collapse = ", ")) + type_fn <- sprintf("[%s]", paste(type_funs, collapse = ", ")) + } + } + + object( + type, + sprintf("List<%s>", type_fn), + check = function(x) { + if (!is.list(x)) stop("Expected a list") + x + } + ) +} + + +#' Typed dataframe +#' +#' This is essentially a list, but the elements must have names and are all the same length. +#' +#' @export +#' @md +ts_dataframe <- function(...) { + values <- list(...) + type <- "{}" + type_fn <- "" + if (length(values)) { + types <- sapply(values, function(x) x$type) + type_funs <- sapply(values, function(x) x$type_fn) + if (is.null(names(values))) stop("Expected named elements") + + type <- sprintf( + "{\n %s\n}", + paste(names(values), types, sep = ": ", collapse = ",\n ") + ) + type_fn <- sprintf( + "{\n %s\n}", + paste(names(values), type_funs, sep = ": ", collapse = ",\n ") + ) + } + + object( + type, + sprintf("List<%s>", type), + check = function(x) { + if (!is.data.frame(x)) stop("Expected a data frame") + x + } + ) +} diff --git a/README.md b/README.md index e27385c..f188a00 100644 --- a/README.md +++ b/README.md @@ -99,5 +99,5 @@ myfun("hello world") #> Error: Expected a number ts_compile(myfun) -#> const myfun = (x: number | number[]) => Promise)>; +#> const myfun = (x: number | number[]) => Promise)>; ```