0

I would like to use delayed assign to assign a new value to a slot of an S4 object. This assignment is basically a database query, and I only want the database query to be executed when the value is actually used.

But for the sake of testing this will do as well:

testFunction <- function(id = 1){
  print("running query")
  return(id)
}

delayedAssign("test", testFunction(id = 2))

This works. "running query" is only printed when test is called, not on assignment. In contrast to:

test2 <- testFunction(id = 2)

Now I would like to be able to do the same thing, but then on a slot.

delayedAssign("someObject@slotName", testFunction(id = 2))

This unfortunately creates an object names 'someObject@slotName' in the current environment. Any ideas on how to solve this?

John Paul
  • 12,196
  • 6
  • 55
  • 75
wligtenberg
  • 7,695
  • 3
  • 22
  • 28
  • You can only "delayed assign" into environments, not into S4 objects. – hadley Apr 04 '14 at 20:11
  • @hadley Maybe I should make a request to add this to the language? Or do you think that is way too difficult? And then the question is, how do I go about feature requests like that? – wligtenberg Apr 05 '14 at 06:02
  • It would be extremely hard to implement because you'd have to be very careful about not accidentally forcing evaluation, when (e.g.) checking if the object is valid. – hadley Apr 07 '14 at 14:53

1 Answers1

3

This represents somewhat of a hack, with unpleasant implementation details. Here's an S4 class, with an environment as a slot

.B <- setClass("B", representation(b="environment"))

I pay attention to initialization, so each instance gets its own environment (rather than all instances sharing the same environment, which would be the default and appropriate if there were a singleton)

setMethod(initialize, "B",
    function(.Object, ..., b=new.env(parent=emptyenv()))
{
    b[["value"]] <- NA
    callNextMethod(.Object, ..., b=b)
})

Let's define generics to set and retrieve the delay-assigned values

setGeneric("delay<-", function(x, ..., value) standardGeneric("delay<-"))
setGeneric("delay", function(x, ...) standardGeneric("delay"))

then implement the method to assign a value to an element, 'value', in our environment

setReplaceMethod("delay", "B", function(x, ..., value) {
    force(value)                        # don't want to be _too_ lazy
    delayedAssign("value", testFunction(value), assign.env=x@b)
    x
})

and to retrieve it

setMethod("delay", "B", function(x, ...) x@b[["value"]])

Here's the product of our labor...

>     b <- .B()
>     delay(b)
[1] NA
>     delay(b) <- 1  # no type safety; could use, e.g., delay<-,numeric-method
>     delay(b)
[1] "running query"
[1] 1

with some weird reference semantics (because b1 and b share the same environment) that would surprise our user (probably even our user expecting reference semantics)

>     b1 <- b         # reference semantics, delayed
>     delay(b1) <- 2
>     delay(b)
[1] "running query"
[1] 2
Martin Morgan
  • 45,935
  • 7
  • 84
  • 112