ts_function method for defining ocaps
This commit is contained in:
parent
57cdc92a2a
commit
cdbf155b57
@ -1,2 +1,3 @@
|
|||||||
^LICENSE\.md$
|
^LICENSE\.md$
|
||||||
^README\.Rmd$
|
^README\.Rmd$
|
||||||
|
^Makefile$
|
||||||
|
|||||||
@ -15,4 +15,5 @@ Suggests:
|
|||||||
Config/testthat/edition: 3
|
Config/testthat/edition: 3
|
||||||
Imports:
|
Imports:
|
||||||
cli,
|
cli,
|
||||||
js
|
js,
|
||||||
|
rlang
|
||||||
|
|||||||
12
NAMESPACE
12
NAMESPACE
@ -1,9 +1,19 @@
|
|||||||
# Generated by roxygen2: do not edit by hand
|
# Generated by roxygen2: do not edit by hand
|
||||||
|
|
||||||
|
S3method(check_type,default)
|
||||||
|
S3method(check_type,ts_function)
|
||||||
|
S3method(check_type,ts_object)
|
||||||
|
S3method(get_type,default)
|
||||||
|
S3method(get_type,ts_function)
|
||||||
|
S3method(get_type,ts_object)
|
||||||
|
S3method(print,ts_function)
|
||||||
S3method(print,ts_object)
|
S3method(print,ts_object)
|
||||||
S3method(ts_compile,character)
|
S3method(ts_compile,character)
|
||||||
S3method(ts_compile,default)
|
S3method(ts_compile,default)
|
||||||
S3method(ts_compile,ts_function)
|
S3method(ts_compile,ts_function)
|
||||||
|
export(check_type)
|
||||||
|
export(get_type)
|
||||||
|
export(is_ts_object)
|
||||||
export(ts_character)
|
export(ts_character)
|
||||||
export(ts_compile)
|
export(ts_compile)
|
||||||
export(ts_dataframe)
|
export(ts_dataframe)
|
||||||
@ -12,4 +22,6 @@ export(ts_function)
|
|||||||
export(ts_integer)
|
export(ts_integer)
|
||||||
export(ts_list)
|
export(ts_list)
|
||||||
export(ts_logical)
|
export(ts_logical)
|
||||||
|
export(ts_null)
|
||||||
export(ts_numeric)
|
export(ts_numeric)
|
||||||
|
export(ts_void)
|
||||||
|
|||||||
48
R/function.R
48
R/function.R
@ -1,6 +1,5 @@
|
|||||||
parse_args <- function(x, mc) {
|
parse_args <- function(x, mc) {
|
||||||
fmls <- lapply(x, eval)
|
fmls <- lapply(x, eval)
|
||||||
mc <- mc[-1]
|
|
||||||
if (!all(names(mc) %in% names(fmls))) {
|
if (!all(names(mc) %in% names(fmls))) {
|
||||||
stop(
|
stop(
|
||||||
"Invalid argument(s): ",
|
"Invalid argument(s): ",
|
||||||
@ -49,7 +48,7 @@ ts_result <- function(type, value) {
|
|||||||
#' @param ... argument definitions (only required if f does not specify these in its formals)
|
#' @param ... argument definitions (only required if f does not specify these in its formals)
|
||||||
#' @param result return type (ignored if overloads are provided)
|
#' @param result return type (ignored if overloads are provided)
|
||||||
#' @export
|
#' @export
|
||||||
ts_function <- function(f, ..., result = NULL) {
|
ts_function <- function(f, ..., result = ts_void()) {
|
||||||
args <- list(...)
|
args <- list(...)
|
||||||
if (!is.null(result) && !is_ts_object(result)) {
|
if (!is.null(result) && !is_ts_object(result)) {
|
||||||
stop("Invalid return type")
|
stop("Invalid return type")
|
||||||
@ -59,13 +58,42 @@ ts_function <- function(f, ..., result = NULL) {
|
|||||||
args <- lapply(formals(f), eval)
|
args <- lapply(formals(f), eval)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn <- function(...) {
|
e <- new.env()
|
||||||
mc <- match.call(f)
|
e$f <- f
|
||||||
x <- parse_args(args, mc)
|
# e$env <- env
|
||||||
result$check(do.call(f, x))
|
e$args <- args
|
||||||
|
e$result <- result
|
||||||
|
|
||||||
|
e$call <- function(...) {
|
||||||
|
mc <- match.call(e$f)
|
||||||
|
.args <- parse_args(args, mc[-1])
|
||||||
|
.res <- do.call(e$f, .args)
|
||||||
|
check_type(result, .res)
|
||||||
}
|
}
|
||||||
attr(fn, "args") <- args
|
|
||||||
attr(fn, "result") <- result
|
e$copy <- function(env = parent.frame()) {
|
||||||
class(fn) <- c("ts_function", class(f))
|
e2 <- e
|
||||||
fn
|
environment(e2$f) <- rlang::env_clone(environment(e$f), env)
|
||||||
|
e2
|
||||||
|
}
|
||||||
|
|
||||||
|
class(e) <- "ts_function"
|
||||||
|
e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
print.ts_function <- function(x, ...) {
|
||||||
|
cli::cli_h3("Ocap function")
|
||||||
|
|
||||||
|
cli::cli_text("Arguments:")
|
||||||
|
args <- lapply(x$args, \(z) z$input_type)
|
||||||
|
lapply(names(args), \(n) {
|
||||||
|
cat("- ", n, ": ", args[[n]], "\n", sep = "")
|
||||||
|
})
|
||||||
|
cli::cli_text("\n\n")
|
||||||
|
|
||||||
|
cli::cli_text("Return type:")
|
||||||
|
cat(x$result$return_type)
|
||||||
}
|
}
|
||||||
|
|||||||
98
R/types.R
98
R/types.R
@ -36,6 +36,7 @@ print.ts_object <- function(x, ...) {
|
|||||||
}
|
}
|
||||||
cli::cli_h3("Return type: ")
|
cli::cli_h3("Return type: ")
|
||||||
if (nchar(x$return_type) > 50) {
|
if (nchar(x$return_type) > 50) {
|
||||||
|
print(x$return_type)
|
||||||
cat(js::uglify_reformat(x$return_type, beautify = TRUE), "\n")
|
cat(js::uglify_reformat(x$return_type, beautify = TRUE), "\n")
|
||||||
} else {
|
} else {
|
||||||
cat(x$return_type, "\n")
|
cat(x$return_type, "\n")
|
||||||
@ -43,12 +44,74 @@ print.ts_object <- function(x, ...) {
|
|||||||
cli::cli_end()
|
cli::cli_end()
|
||||||
}
|
}
|
||||||
|
|
||||||
#' @describeIn object Check if an object is a ts object
|
#' @describeIn ts_object Check if an object is a ts object
|
||||||
#' @export
|
#' @export
|
||||||
is_ts_object <- function(x) {
|
is_ts_object <- function(x) {
|
||||||
inherits(x, "ts_object")
|
inherits(x, "ts_object")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#' @describeIn ts_object Get the input type of a ts object
|
||||||
|
#' @param x A ts object
|
||||||
|
#' @param which Which type to get, either "input" or "return"
|
||||||
|
#' @export
|
||||||
|
get_type <- function(x, which = c("input", "return")) {
|
||||||
|
UseMethod("get_type")
|
||||||
|
}
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
get_type.ts_object <- function(x, which = c("input", "return")) {
|
||||||
|
which <- match.arg(which)
|
||||||
|
if (which == "input") {
|
||||||
|
return(x$input_type)
|
||||||
|
}
|
||||||
|
x$return_type
|
||||||
|
}
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
get_type.ts_function <- function(x, which = c("input", "return")) {
|
||||||
|
which <- match.arg(which)
|
||||||
|
if (which == "input") {
|
||||||
|
return("z.function()")
|
||||||
|
}
|
||||||
|
"Robj.ocap()"
|
||||||
|
}
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
get_type.default <- function(x, which) {
|
||||||
|
stop("Invalid object")
|
||||||
|
}
|
||||||
|
|
||||||
|
#' @describeIn ts_object Check if an object has the correct type
|
||||||
|
#' @param type A ts object
|
||||||
|
#' @param x An object
|
||||||
|
#' @export
|
||||||
|
check_type <- function(type, x) UseMethod("check_type")
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
check_type.default <- function(type, x) {
|
||||||
|
stop("Invalid object")
|
||||||
|
}
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
check_type.ts_object <- function(type, x) {
|
||||||
|
type$check(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
check_type.ts_function <- function(type, x) {
|
||||||
|
if ("ts_function" %in% class(x)) {
|
||||||
|
return(x)
|
||||||
|
}
|
||||||
|
if (!is.function(x)) stop("Expected a function")
|
||||||
|
do.call(
|
||||||
|
ts_function,
|
||||||
|
c(
|
||||||
|
list(x), attr(type, "args"),
|
||||||
|
list(result = attr(type, "result"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ts_union <- function(...) sprintf("z.union([%s])", paste(..., sep = ", "))
|
ts_union <- function(...) sprintf("z.union([%s])", paste(..., sep = ", "))
|
||||||
ts_array <- function(type = c("z.number()", "z.boolean()", "z.string()")) {
|
ts_array <- function(type = c("z.number()", "z.boolean()", "z.string()")) {
|
||||||
if (type == "z.number()") {
|
if (type == "z.number()") {
|
||||||
@ -182,8 +245,8 @@ ts_list <- function(...) {
|
|||||||
type <- "z.union([z.object({}), z.array(z.any())])"
|
type <- "z.union([z.object({}), z.array(z.any())])"
|
||||||
type_fn <- ""
|
type_fn <- ""
|
||||||
if (length(values)) {
|
if (length(values)) {
|
||||||
types <- sapply(values, function(x) x$input_type)
|
types <- sapply(values, get_type, which = "input")
|
||||||
type_funs <- sapply(values, function(x) x$return_type)
|
type_funs <- sapply(values, get_type, which = "return")
|
||||||
if (!is.null(names(values))) {
|
if (!is.null(names(values))) {
|
||||||
type <- sprintf(
|
type <- sprintf(
|
||||||
"{ %s }",
|
"{ %s }",
|
||||||
@ -216,7 +279,7 @@ ts_list <- function(...) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i in seq_along(values)) {
|
for (i in seq_along(values)) {
|
||||||
values[[i]]$check(x[[i]])
|
x[[i]] <- check_type(values[[i]], x[[i]])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
x
|
x
|
||||||
@ -236,8 +299,8 @@ ts_dataframe <- function(...) {
|
|||||||
type <- "z.record(z.string(), z.any())"
|
type <- "z.record(z.string(), z.any())"
|
||||||
type_fn <- ""
|
type_fn <- ""
|
||||||
if (length(values)) {
|
if (length(values)) {
|
||||||
types <- sapply(values, function(x) x$input_type)
|
types <- sapply(values, get_type, which = "input")
|
||||||
type_funs <- sapply(values, function(x) x$return_type)
|
type_funs <- sapply(values, get_type, which = "return")
|
||||||
if (is.null(names(values))) stop("Expected named elements")
|
if (is.null(names(values))) stop("Expected named elements")
|
||||||
|
|
||||||
type <- sprintf(
|
type <- sprintf(
|
||||||
@ -259,3 +322,26 @@ ts_dataframe <- function(...) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
ts_null <- function() {
|
||||||
|
ts_object(
|
||||||
|
"z.null()",
|
||||||
|
"Robj.null()",
|
||||||
|
check = function(x) {
|
||||||
|
if (!is.null(x)) stop("Expected NULL")
|
||||||
|
x
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#' @export
|
||||||
|
ts_void <- function() {
|
||||||
|
ts_object(
|
||||||
|
"z.void()",
|
||||||
|
"null",
|
||||||
|
check = function(x) {
|
||||||
|
return(NULL)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
28
README.Rmd
28
README.Rmd
@ -36,18 +36,24 @@ Writing functions is easy, just use the `ts_*()` functions to define formals and
|
|||||||
|
|
||||||
```r
|
```r
|
||||||
library(ts)
|
library(ts)
|
||||||
app <- ts_list(
|
addFn <- ts_function(
|
||||||
add = ts_fun(
|
function(a = ts_numeric(1), b = ts_numeric(1)) a + b,
|
||||||
function(x = ts_number(1), y = ts_number(1)) a + b,
|
result = ts_numeric(1)
|
||||||
ts_numeric(1)
|
)
|
||||||
),
|
sampleFn <- ts_function(
|
||||||
sample = ts_fun(
|
function(x = ts_character(), n = ts_integer(1)) sample(x, n),
|
||||||
function(x, n) {
|
result = ts_character()
|
||||||
sample(x, n)
|
)
|
||||||
|
app <- ts_function(
|
||||||
|
function() {
|
||||||
|
list(
|
||||||
|
add = addFn,
|
||||||
|
sample = sampleFn
|
||||||
|
)
|
||||||
},
|
},
|
||||||
x = ts_string(),
|
result = ts_list(
|
||||||
n = ts_number(1),
|
add = appFn,
|
||||||
result = r_character()
|
sample = sampleFn
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
\alias{ts_function}
|
\alias{ts_function}
|
||||||
\title{TS function definition}
|
\title{TS function definition}
|
||||||
\usage{
|
\usage{
|
||||||
ts_function(f, ..., result = NULL)
|
ts_function(f, ..., result = ts_void())
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{f}{an R function}
|
\item{f}{an R function}
|
||||||
|
|||||||
@ -4,7 +4,10 @@
|
|||||||
\alias{ts_list}
|
\alias{ts_list}
|
||||||
\title{Typed list}
|
\title{Typed list}
|
||||||
\usage{
|
\usage{
|
||||||
ts_list(values = NULL)
|
ts_list(...)
|
||||||
|
}
|
||||||
|
\arguments{
|
||||||
|
\item{...}{A list of types, named or unnamed.}
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
A list is a vector of other robjects, which may or may not be named.
|
A list is a vector of other robjects, which may or may not be named.
|
||||||
|
|||||||
@ -1,16 +1,25 @@
|
|||||||
% Generated by roxygen2: do not edit by hand
|
% Generated by roxygen2: do not edit by hand
|
||||||
% Please edit documentation in R/types.R
|
% Please edit documentation in R/types.R
|
||||||
\name{object}
|
\name{ts_object}
|
||||||
\alias{object}
|
\alias{ts_object}
|
||||||
|
\alias{is_ts_object}
|
||||||
|
\alias{get_type}
|
||||||
|
\alias{check_type}
|
||||||
\title{Typed object}
|
\title{Typed object}
|
||||||
\usage{
|
\usage{
|
||||||
object(
|
ts_object(
|
||||||
input_type = "any",
|
input_type = "any",
|
||||||
return_type = "any",
|
return_type = "any",
|
||||||
default = NULL,
|
default = NULL,
|
||||||
check = function() stop("Not implemented"),
|
check = function() stop("Not implemented"),
|
||||||
generic = FALSE
|
generic = FALSE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is_ts_object(x)
|
||||||
|
|
||||||
|
get_type(x, which = c("input", "return"))
|
||||||
|
|
||||||
|
check_type(type, x)
|
||||||
}
|
}
|
||||||
\arguments{
|
\arguments{
|
||||||
\item{input_type}{The type of the object that Typescript expect to send to R.}
|
\item{input_type}{The type of the object that Typescript expect to send to R.}
|
||||||
@ -22,7 +31,22 @@ object(
|
|||||||
\item{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.}
|
\item{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.}
|
||||||
|
|
||||||
\item{generic}{logical, if \code{TRUE} then the object is a generic type.}
|
\item{generic}{logical, if \code{TRUE} then the object is a generic type.}
|
||||||
|
|
||||||
|
\item{x}{An object}
|
||||||
|
|
||||||
|
\item{which}{Which type to get, either "input" or "return"}
|
||||||
|
|
||||||
|
\item{type}{A ts object}
|
||||||
}
|
}
|
||||||
\description{
|
\description{
|
||||||
This is the base type for all typed objects. It is not meant to be used directly.
|
This is the base type for all typed objects. It is not meant to be used directly.
|
||||||
}
|
}
|
||||||
|
\section{Functions}{
|
||||||
|
\itemize{
|
||||||
|
\item \code{is_ts_object()}: Check if an object is a ts object
|
||||||
|
|
||||||
|
\item \code{get_type()}: Get the input type of a ts object
|
||||||
|
|
||||||
|
\item \code{check_type()}: Check if an object has the correct type
|
||||||
|
|
||||||
|
}}
|
||||||
@ -7,8 +7,8 @@ test_that("anonomous function definitions", {
|
|||||||
result = ts_numeric(1)
|
result = ts_numeric(1)
|
||||||
)
|
)
|
||||||
|
|
||||||
expect_equal(add(1, 2), 3)
|
expect_equal(add$call(1, 2), 3)
|
||||||
expect_error(add("a", 2))
|
expect_error(add$call("a", 2))
|
||||||
})
|
})
|
||||||
|
|
||||||
test_that("named function definitions", {
|
test_that("named function definitions", {
|
||||||
@ -18,26 +18,51 @@ test_that("named function definitions", {
|
|||||||
result = ts_numeric()
|
result = ts_numeric()
|
||||||
)
|
)
|
||||||
|
|
||||||
x <- sample_num(1:10)
|
x <- sample_num$call(1:10)
|
||||||
expect_true(all(x %in% 1:10))
|
expect_true(all(x %in% 1:10))
|
||||||
expect_error(sample_num("a"))
|
expect_error(sample_num("a"))
|
||||||
})
|
})
|
||||||
|
|
||||||
test_that("function with complex return types", {
|
test_that("void return types", {
|
||||||
sampler <- ts_function(
|
print_x <- ts_function(
|
||||||
function(x = ts_numeric()) {
|
function(x = ts_numeric()) {
|
||||||
|
print(x)
|
||||||
|
return(NULL)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
expect_output(z <- print_x$call(1:10))
|
||||||
|
expect_null(z)
|
||||||
|
})
|
||||||
|
|
||||||
|
test_that("function with complex return types", {
|
||||||
|
get_sample <- ts_function(
|
||||||
|
function(n = ts_numeric(1)) {
|
||||||
|
sample(values, n)
|
||||||
|
},
|
||||||
|
result = ts_numeric()
|
||||||
|
)
|
||||||
|
|
||||||
|
sampler <- ts_function(
|
||||||
|
function(values = ts_numeric()) {
|
||||||
list(
|
list(
|
||||||
get = function(n) sample(x, n)
|
get = get_sample$copy(),
|
||||||
|
set = ts_function(
|
||||||
|
function(value = ts_numeric()) {
|
||||||
|
values <<- value
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
result = ts_list(
|
result = ts_list(
|
||||||
get = ts_function(
|
get = get_sample,
|
||||||
NULL,
|
set = ts_function(NULL, value = ts_numeric())
|
||||||
n = ts_integer(1),
|
|
||||||
result = ts_numeric(1)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
s <- sampler(1:10)
|
s <- sampler$call(1:10)
|
||||||
|
expect_type(s$get$call(2), "integer")
|
||||||
|
|
||||||
|
expect_silent(s$set$call(100:200))
|
||||||
|
expect_gte(s$get$call(1), 100)
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user