vetr icon indicating copy to clipboard operation
vetr copied to clipboard

Vetting expressions for recursive objects (at least of depth one)

Open franknarf1 opened this issue 8 years ago • 4 comments

I'd like to inequality-test my data.frame's columns. This seems to work as long as I have atomic columns:

vetd = function(tmpexpr, x){
    stopifnot(length(x) + 1L == length(tmpexpr))

    z = eval(tmpexpr[[1]])()
    vet(target = z, current = x, stop = TRUE)

    for (j in seq_along(x)) 
      do.call(vet, list(target = tmpexpr[[j+1L]], current = x[[j]], stop = TRUE))
}

tt = quote(data.frame(ct = INT.POS, price = NUM.POS))
vetd(tt, data.frame(ct = 1L, price = 1))
# ok
vetd(tt, data.frame(ct = 1L, price = -1))
# Error in (function (target, current, env = parent.frame(), format = "text",  : 
#   `-1` should contain only positive values, but has negatives

But this is fairly clunky and wouldn't allow for use like

f = function(d, v){ 
  vetr(d = quote(data.frame(ct = INT.POS, price = NUM.POS)), v = INT.1)
  TRUE
}

I realize it would mess with the elegance of the package to special-case for data.frames, but I guess this would be one of my main use-cases for vetting.

Side note: For the second test in vetd, I tried vet(eval(tmpexpr[[1]])(), x, stop=TRUE) and even do.call(vet, list(target = z, current = x, stop = TRUE)), but neither of these went through. I'm not sure if they're meant to.

franknarf1 avatar Jul 26 '17 17:07 franknarf1

Unfortunately templates only allow direct checking of structure. To check values you have to have explicit checks. Explicit checks are tokens that involve .:

> d.tpl <- data.frame(ct=integer(), price=numeric())
> f <- function(d, v) {
+   vetr(
+     d=d.tpl && .$ct > 0 && .$price > 0,
+     v=INT.1
+   )
+   TRUE
+ }
> f(data.frame(ct=1L, price=1), 1L)
[1] TRUE
> f(data.frame(ct=1L, price=-1), 1L)
Error in f(d = data.frame(ct = 1L, price = -1), v = 1L) :
  For argument `d`, `data.frame(ct = 1L, price = -1)$price > 0` is not TRUE (FALSE)

Does this address your question?

Alternative:

  vetr(
    d=d.tpl && vapply(., `>`, logical(1L), 0),
    v=INT.1
  )

brodieG avatar Jul 26 '17 17:07 brodieG

One possible implementation that might address what you want, which if I understand correctly is to place vetting expressions in the structure (currently not possible), would be to have a special object (probably s4 so no collisions possible) that contains a validation expression. So for example:

df.tpl <- data.frame(ct=vetExp(INT.POS), price=vetExp(NUM.POS))
f <- function(d) vetr(df.tpl)

We'd have to get around the fact that the data.frame constructor will probably freak out about s4 columns, but there are ways to deal with that.

Let me know if I'm understanding what you're after correctly. This would be a pretty useful feature, and the implementation complexity isn't massive, though not trivial by any means.

brodieG avatar Jul 26 '17 17:07 brodieG

Yeah, that works, though it is clunky to (i) write the column names twice and (ii) not have the conditions embedded in a template or token, as I could with atomic vectors, like...

library(vetr)
library(data.table)
STORE.INT = vet_token(storage.mode(.) == "integer", "%sis not stored as integer")
IDATE = vet_token(as.IDate("1970-01-01")[0L] && STORE.INT)

add_week = function(d){
  vetr(IDATE)
  d + 7L
}

x = structure(Sys.Date(), class = c("IDate", "Date"))
# ^ example that looks like IDate but isn't
add_week(x)
# Error in add_week(d = x) : For argument `d`, `x` is not stored as integer
add_week(as.IDate(x))
# ok

Edit: Ah, give me a minute to review your second comment.

Let me know if I'm understanding what you're after correctly. This would be a pretty useful feature, and the implementation complexity isn't massive, though not trivial by any means.

Yup, that's correct, though I'd also be fine with the whole thing being one expression (rather than embedded, like in my example).

franknarf1 avatar Jul 26 '17 17:07 franknarf1

Aside, you can actually do:

f <- function(d, v) {
  vetr(d=d.tpl && . > 0, v=INT.1)
  TRUE
}

This just happens to be a specialish case of how operators work on data frames. You can even subset the columns. But I think your request still stands for other objects.

brodieG avatar Jul 26 '17 18:07 brodieG