basic attempt at creating overloaded functions

This commit is contained in:
Tom Elliott 2024-07-29 16:27:38 +12:00
parent e641b139b8
commit c65ac9c3a1
13 changed files with 132 additions and 22 deletions

View File

@ -4,6 +4,8 @@ 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)
S3method(ts_compile,ts_overload)
export(is_overload)
export(ts_character) export(ts_character)
export(ts_compile) export(ts_compile)
export(ts_dataframe) export(ts_dataframe)
@ -13,3 +15,4 @@ export(ts_integer)
export(ts_list) export(ts_list)
export(ts_logical) export(ts_logical)
export(ts_numeric) export(ts_numeric)
export(ts_overload)

View File

@ -1,10 +1,10 @@
#' @export #' @export
ts_compile <- function(f, file = NULL, ...) { ts_compile <- function(f, ..., file = NULL) {
UseMethod("ts_compile") UseMethod("ts_compile")
} }
#' @export #' @export
ts_compile.ts_function <- function(f, file = NULL, name = deparse(substitute(f))) { ts_compile.ts_function <- function(f, name = deparse(substitute(f)), ...) {
inputs <- attr(f, "args") inputs <- attr(f, "args")
result <- attr(f, "result") result <- attr(f, "result")
@ -14,10 +14,17 @@ ts_compile.ts_function <- function(f, file = NULL, name = deparse(substitute(f))
sprintf("const %s = (%s) => Promise<%s>;", name, fn_args, result$type_fn) sprintf("const %s = (%s) => Promise<%s>;", name, fn_args, result$type_fn)
} }
#' @export
ts_compile.ts_overload <- function(f, file = NULL, name = deparse(substitute(f))) {
cmt <- sprintf("\n// %s overloads", name)
oloads <- sapply(f, ts_compile, name = name)
paste(cmt, paste(oloads, collapse = "\n"), sep = "\n")
}
#' @export #' @export
ts_compile.character <- function( ts_compile.character <- function(
f, f,
file = sprintf("%s.ts", tools::file_path_sans_ext(f))) { file = sprintf("%s.d.ts", tools::file_path_sans_ext(f))) {
if (length(f) > 1) { if (length(f) > 1) {
return(sapply(f, ts_compile)) return(sapply(f, ts_compile))
} }
@ -53,6 +60,6 @@ ts_compile.character <- function(
} }
#' @export #' @export
ts_compile.default <- function(f) { ts_compile.default <- function(f, ...) {
warning("Not supported") warning("Not supported")
} }

View File

@ -14,13 +14,27 @@ parse_args <- function(x, mc) {
args args
} }
# TS function #' TS function definition
#'
#' @param f an R function
#' @param ... argument definitions, OR function overloads
#' @param result return type (ignored if overloads are provided)
#' @export #' @export
ts_function <- function(f, ..., result = NULL) { ts_function <- function(f, ..., result = NULL) {
args <- list(...) args <- list(...)
if (!is_object(result)) { if (!is.null(result) && !is_object(result)) {
stop("Invalid return type") stop("Invalid return type")
} }
if (any(is_overload(args))) {
if (!all(is_overload(args))) {
stop("Cannot mix overloads with standard arguments")
}
z <- lapply(args, function(x) {
do.call(ts_function, c(list(f), x$args, list(result = x$result)))
})
class(z) <- "ts_overload"
return(z)
}
fn <- function(...) { fn <- function(...) {
mc <- match.call(f) mc <- match.call(f)
@ -32,3 +46,15 @@ ts_function <- function(f, ..., result = NULL) {
class(fn) <- c("ts_function", class(f)) class(fn) <- c("ts_function", class(f))
fn fn
} }
#' @export
is_overload <- function(x) {
sapply(x, inherits, what = "ts_overload")
}
#' @export
ts_overload <- function(..., result = NULL) {
structure(list(args = list(...), result = result),
class = "ts_overload"
)
}

View File

@ -6,12 +6,14 @@
#' @param type_fn The type of the object that Typescript expects to recieve from 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 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. #' @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.
#' @param generic logical, if `TRUE` then the object is a generic type.
#' #'
#' @md #' @md
object <- function(type = "any", object <- function(type = "any",
type_fn = "any", type_fn = "any",
default = NULL, default = NULL,
check = function() stop("Not implemented")) { check = function() stop("Not implemented"),
generic = FALSE) {
e <- environment() e <- environment()
e$attr <- function(name, value) { e$attr <- function(name, value) {

View File

@ -102,9 +102,9 @@ myfun(1:5)
myfun("hello world") myfun("hello world")
cat(readLines("tests/testthat/app/app.R"), sep = "\n") cat(readLines("tests/testthat/app.R"), sep = "\n")
ts_compile("tests/testthat/app/app.R", file = "") ts_compile("tests/testthat/app.R", file = "")
``` ```
## TODO ## TODO
@ -114,3 +114,5 @@ ts_compile("tests/testthat/app/app.R", file = "")
- [ ] Add support for conditional return types - [ ] Add support for conditional return types
e.g., `const sample = <T, N extends number>(x: T[], n: N) => N extends 1 ? T : T[]` e.g., `const sample = <T, N extends number>(x: T[], n: N) => N extends 1 ? T : T[]`
- [ ] Function overloads? Perhaps just a wrapper around several function definitions...

View File

@ -98,19 +98,35 @@ myfun(1:5)
myfun("hello world") myfun("hello world")
#> Error: Expected a number #> Error: Expected a number
cat(readLines("tests/testthat/app/app.R"), sep = "\n") cat(readLines("tests/testthat/app.R"), sep = "\n")
#> library(ts) #> library(ts)
#> #>
#> fn_mean <- ts_function(mean, x = ts_numeric(), result = ts_numeric(1)) #> fn_mean <- ts_function(mean, x = ts_numeric(), result = ts_numeric(1))
#> fn_first <- ts_function(function(x) x[1], #> fn_first <- ts_function(function(x) x[1],
#> x = ts_character(-1), result = ts_character(1) #> x = ts_character(-1), result = ts_character(1)
#> ) #> )
#>
#> sample_one <- ts_function(
#> sample,
#> ts_overload(
#> x = ts_numeric(),
#> result = ts_numeric(1)
#> ),
#> ts_overload(
#> x = ts_character(),
#> result = ts_character(1)
#> )
#> )
ts_compile("tests/testthat/app/app.R", file = "") ts_compile("tests/testthat/app.R", file = "")
#> import type { Character, Numeric } from 'rserve-ts'; #> import type { Character, Numeric } from 'rserve-ts';
#> #>
#> const fn_first = (x: string | string[]) => Promise<Character<1>)>; #> const fn_first = (x: string | string[]) => Promise<Character<1>)>;
#> const fn_mean = (x: number | number[]) => Promise<Numeric<1>)>; #> const fn_mean = (x: number | number[]) => Promise<Numeric<1>)>;
#>
#> // sample_one overloads
#> const sample_one = (x: number | number[]) => Promise<Numeric<1>)>;
#> const sample_one = (x: string | string[]) => Promise<Character<1>)>;
``` ```
## TODO ## TODO
@ -123,3 +139,6 @@ ts_compile("tests/testthat/app/app.R", file = "")
e.g., `const sample = <T, N extends number>(x: T[], n: N) => N e.g., `const sample = <T, N extends number>(x: T[], n: N) => N
extends 1 ? T : T[]` extends 1 ? T : T[]`
- [ ] Function overloads? Perhaps just a wrapper around several
function definitions…

View File

@ -8,7 +8,8 @@ object(
type = "any", type = "any",
type_fn = "any", type_fn = "any",
default = NULL, default = NULL,
check = function() stop("Not implemented") check = function() stop("Not implemented"),
generic = FALSE
) )
} }
\arguments{ \arguments{
@ -19,6 +20,8 @@ object(
\item{default}{The default value of the object.} \item{default}{The default value of the 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.}
} }
\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.

18
man/ts_function.Rd Normal file
View File

@ -0,0 +1,18 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/function.R
\name{ts_function}
\alias{ts_function}
\title{TS function definition}
\usage{
ts_function(f, ..., result = NULL)
}
\arguments{
\item{f}{an R function}
\item{...}{argument definitions, OR function overloads}
\item{result}{return type (ignored if overloads are provided)}
}
\description{
TS function definition
}

18
tests/testthat/app.R Normal file
View File

@ -0,0 +1,18 @@
library(ts)
fn_mean <- ts_function(mean, x = ts_numeric(), result = ts_numeric(1))
fn_first <- ts_function(function(x) x[1],
x = ts_character(-1), result = ts_character(1)
)
sample_one <- ts_function(
sample,
ts_overload(
x = ts_numeric(),
result = ts_numeric(1)
),
ts_overload(
x = ts_character(),
result = ts_character(1)
)
)

5
tests/testthat/app.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import type { Character, Numeric, PE.Numeric<1>, } from 'rserve-ts';
const fn_first = (x: string | string[]) => Promise<Character<1>)>;
const fn_mean = (x: number | number[]) => Promise<Numeric<1>)>;
c("const sample_one = (x: number | number[]) => Promise<Numeric<1>)>;", "const sample_one = (x: string | string[]) => Promise<Character<1>)>;")

View File

@ -1,6 +0,0 @@
library(ts)
fn_mean <- ts_function(mean, x = ts_numeric(), result = ts_numeric(1))
fn_first <- ts_function(function(x) x[1],
x = ts_character(-1), result = ts_character(1)
)

View File

@ -1,4 +0,0 @@
import { types } from 'rserve-ts';
const fn_first = (x: string | string[]) => Promise<RTYPE.Character<1>)>;
const fn_mean = (x: number | number[]) => Promise<RTYPE.Numeric<1>)>;

View File

@ -0,0 +1,17 @@
# overload input/return types
sample_one <- ts_function(
sample,
ts_overload(
x = ts_numeric(),
result = ts_numeric(1)
),
ts_overload(
x = ts_character(),
result = ts_character(1)
)
)
ts_compile(sample_one)
# compile to:
# const sample_one = (x: number) => Promise<number>;
# const sample_one = (x: string) => Promise<string>;