Henrik Bengtsson
2025-Mar-19 17:58 UTC
[Rd] structure(<primitive function>, ...) is sticky: a bug, or should it be an error?
Hello. I just (re-)discovered that structure(sum, init = 100) is "sticky", i.e. it stays with base::sum(). Here's an minimal example: $ R --vanilla --quiet> void <- structure(sum, some_attr = TRUE) > str(sum)function (..., na.rm = FALSE) - attr(*, "some_attr")= logi TRUE>From my very basic troubleshooting, it looks like this is happeningfor primitive functions. I think I understand that this comes down to primitive functions cannot be copied and baseenv() being special, i.e. in structure() there will be no copy made of the primitive function, and then attributes()<- ends up modifying the original primitive function. Even if this is a documented feature, I believe, it is a silent feature with risky side effects. We might already have code out there that silently produces incorrect results, because of this behavior. For example, I was about to a custom reduce() function where I control the initial value via an "init" attribute of the reducer function, e.g. x <- 1:10 sum1 <- reduce(x, `+`) sum2 <- reduce(x, structure(`+`, init = 100)) # == 100 + sum1 If I then call: sum3 <- reduce(x, `+`) the 'init' attribute that was set in the sum2 statement will affect sum3 such that sum3 == sum2, not sum3 == sum1 as one would expect. SUGGESTIONS: If this is a bug, then I think it needs to be fixed. If it cannot be fixed, maybe this could be protected against, e.g.> void <- structure(sum, some_attr = TRUE)Error: You must not set attributes on a primitive function: sum Maybe it's sufficient to implement a protection against this in attr()<-, attributes()<-, and class()<-. Comments? /Henrik PS. I've verified this with R 4.4.3 and R Under development (unstable) (2025-03-19 r88003).
Martin Maechler
2025-Mar-20 10:31 UTC
[Rd] structure(<primitive function>, ...) is sticky: a bug, or should it be an error?
>>>>> Henrik Bengtsson >>>>> on Wed, 19 Mar 2025 10:58:46 -0700 writes:> Hello. > I just (re-)discovered that structure(sum, init = 100) is "sticky", > i.e. it stays with base::sum(). Here's an minimal example: > $ R --vanilla --quiet >> void <- structure(sum, some_attr = TRUE) >> str(sum) > function (..., na.rm = FALSE) > - attr(*, "some_attr")= logi TRUE >> From my very basic troubleshooting, it looks like this is happening > for primitive functions. I think I understand that this comes down to > primitive functions cannot be copied and baseenv() being special, i.e. > in structure() there will be no copy made of the primitive function, > and then attributes()<- ends up modifying the original primitive > function. Even if this is a documented feature, I believe, it is a > silent feature with risky side effects. We might already have code out > there that silently produces incorrect results, because of this > behavior. For example, I was about to a custom reduce() function where > I control the initial value via an "init" attribute of the reducer > function, e.g. > x <- 1:10 > sum1 <- reduce(x, `+`) > sum2 <- reduce(x, structure(`+`, init = 100)) # == 100 + sum1 > If I then call: > sum3 <- reduce(x, `+`) > the 'init' attribute that was set in the sum2 statement will affect > sum3 such that sum3 == sum2, not sum3 == sum1 as one would expect. > SUGGESTIONS: > If this is a bug, then I think it needs to be fixed. If it cannot be > fixed, maybe this could be protected against, e.g. >> void <- structure(sum, some_attr = TRUE) > Error: You must not set attributes on a primitive function: sum > Maybe it's sufficient to implement a protection against this in > attr()<-, attributes()<-, and class()<-. > Comments? > /Henrik Yes, this is a bug -- a version of your code above:> void <- structure(sum, foo = TRUE) > identical(void, sum)[1] TRUE> sumfunction (..., na.rm = FALSE) .Primitive("sum") attr(,"foo") [1] TRUE>Above, you are already looking at ways to deal with it; and that *is* more delicate, indeed: One thing we (R core) found previously (a couple of years ago) was that the `structure(..)` function was already too "slow" for it to be used in some base R functions, and adding another if(..) to it will not help making it faster ... OTOH, structure() being a pure R function (no direct .Internal(), .Call() ..) is also important I think, as it keeps its code nicely self documenting. I'm pretty convinced we should fix it by checking for primitive functions inside the C code of `attributes<-` : arguably the bug is really there, rather than in structure(). Patches are welcome (via R's Bugzilla or just here). Martin