Skip to content

Commit c41295a

Browse files
authored
Rework vec_ptype() into an optional generic (#1322)
* Rework `vec_ptype()` into a generic for S3 types * Document that methods can be written for `vec_ptype()` * Test that vec-ptype methods are found * Add NEWS bullet * Use `decl/` folder to hold forward declarations * Use @lionel-'s suggestion in `vec_ptype()` documentation * Tweak NEWS to align better with new docs
1 parent 361a904 commit c41295a

File tree

6 files changed

+68
-3
lines changed

6 files changed

+68
-3
lines changed

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# vctrs (development version)
22

3+
* `vec_ptype()` is now an optional _performance_ generic. It is not necessary
4+
to implement, but if your class has a static prototype, you might consider
5+
implementing a custom `vec_ptype()` method that returns a constant to
6+
improve performance in some cases (such as common type imputation).
7+
38
* New `vec_detect_complete()`, inspired by `stats::complete.cases()`. For most
49
vectors, this is identical to `!vec_equal_na()`. For data frames and
510
matrices, this detects rows that only contain non-missing values.

R/type.R

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@
3939
#' See [internal-faq-ptype2-identity] for more information about
4040
#' identity values.
4141
#'
42+
#' `vec_ptype()` is a _performance_ generic. It is not necessary to implement it
43+
#' because the default method will work for any vctrs type. However the default
44+
#' method builds around other vctrs primitives like `vec_slice()` which incurs
45+
#' performance costs. If your class has a static prototype, you might consider
46+
#' implementing a custom `vec_ptype()` method that returns a constant. This will
47+
#' improve the performance of your class in many cases ([common
48+
#' type][vec_ptype2] imputation in particular).
49+
#'
4250
#' Because it may contain unspecified vectors, the prototype returned
4351
#' by `vec_ptype()` is said to be __unfinalised__. Call
4452
#' [vec_ptype_finalise()] to finalise it. Commonly you will need the
@@ -94,7 +102,8 @@ vec_ptype <- function(x, ..., x_arg = "") {
94102
if (!missing(...)) {
95103
ellipsis::check_dots_empty()
96104
}
97-
.Call(vctrs_ptype, x, x_arg)
105+
return(.Call(vctrs_ptype, x, x_arg))
106+
UseMethod("vec_ptype")
98107
}
99108

100109
#' @export

man/vec_ptype.Rd

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/decl/ptype-decl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#ifndef VCTRS_PTYPE_DECL_H
2+
#define VCTRS_PTYPE_DECL_H
3+
4+
static inline SEXP vec_ptype_method(SEXP x);
5+
static inline SEXP vec_ptype_invoke(SEXP x, SEXP method);
6+
7+
#endif

src/type.c

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
#include "ptype2.h"
55
#include "type-data-frame.h"
66
#include "utils.h"
7+
#include "decl/ptype-decl.h"
78

89
// Initialised at load time
10+
static SEXP syms_vec_ptype = NULL;
11+
912
static SEXP syms_vec_ptype_finalise_dispatch = NULL;
1013
static SEXP fns_vec_ptype_finalise_dispatch = NULL;
1114

@@ -53,6 +56,7 @@ static inline SEXP vec_ptype_slice(SEXP x, SEXP empty) {
5356
return vec_slice(x, R_NilValue);
5457
}
5558
}
59+
5660
static SEXP s3_type(SEXP x, struct vctrs_arg* x_arg) {
5761
switch (class_type(x)) {
5862
case vctrs_class_bare_tibble:
@@ -75,8 +79,32 @@ static SEXP s3_type(SEXP x, struct vctrs_arg* x_arg) {
7579
return x;
7680
}
7781

78-
vec_assert(x, x_arg);
79-
return vec_slice(x, R_NilValue);
82+
SEXP method = PROTECT(vec_ptype_method(x));
83+
84+
SEXP out;
85+
86+
if (method == r_null) {
87+
vec_assert(x, x_arg);
88+
out = vec_slice(x, r_null);
89+
} else {
90+
out = vec_ptype_invoke(x, method);
91+
}
92+
93+
UNPROTECT(1);
94+
return out;
95+
}
96+
97+
static inline
98+
SEXP vec_ptype_method(SEXP x) {
99+
SEXP cls = PROTECT(s3_get_class(x));
100+
SEXP method = s3_class_find_method("vec_ptype", cls, vctrs_method_table);
101+
UNPROTECT(1);
102+
return method;
103+
}
104+
105+
static inline
106+
SEXP vec_ptype_invoke(SEXP x, SEXP method) {
107+
return vctrs_dispatch1(syms_vec_ptype, method, syms_x, x);
80108
}
81109

82110
SEXP df_ptype(SEXP x, bool bare) {
@@ -270,6 +298,8 @@ static SEXP vctrs_type2_common(SEXP current,
270298

271299

272300
void vctrs_init_type(SEXP ns) {
301+
syms_vec_ptype = Rf_install("vec_ptype");
302+
273303
syms_vec_ptype_finalise_dispatch = Rf_install("vec_ptype_finalise_dispatch");
274304
fns_vec_ptype_finalise_dispatch = Rf_findVar(syms_vec_ptype_finalise_dispatch, ns);
275305
}

tests/testthat/test-type.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,13 @@ test_that("the type of a classed data frame with an unspecified column retains u
190190
expect_identical(vec_ptype(df2), expect)
191191
})
192192

193+
test_that("vec_ptype() methods can be written", {
194+
local_methods(
195+
vec_ptype.vctrs_foobar = function(x, ...) "dispatch"
196+
)
197+
expect_identical(vec_ptype(foobar()), "dispatch")
198+
})
199+
193200
test_that("vec_ptype_finalise() works with NULL", {
194201
expect_identical(vec_ptype_finalise(NULL), NULL)
195202
})

0 commit comments

Comments
 (0)