Skip to content

incoherent treatment of NULL

7 messages · Wacek Kusnierczyk, Martin Maechler

#
somewhat related to a previous discussion [1] on how 'names<-' would
sometimes modify its argument in place, and sometimes produce a modified
copy without changing the original, here's another example of how it
becomes visible to the user when r makes or doesn't make a copy of an
object:

    x = NULL
    dput(x)
    # NULL
    class(x) = 'integer'
    # error: invalid (NULL) left side of assignment

    x = c()
    dput(x)
    # NULL
    class(x) = 'integer'
    dput(x)
    # integer(0)

in both cases, x ends up with the value NULL (the no-value object).  in
both cases, dput explains that x is NULL.  in both cases, an attempt is
made to make x be an empty integer vector.  the first fails, because it
tries to modify NULL itself, the latter apparently does not and succeeds.

however, the following has a different pattern:

    x = NULL
    dput(x)
    # NULL
    names(x) = character(0)
    # error: attempt to set an attribute on NULL

    x = c()
    dput(x)
    # NULL
    names(x) = character(0)
    # error: attempt to set an attribute on NULL

and also:

    x = c()
    class(x) = 'integer'
    # fine
    class(x) = 'foo'
    # error: attempt to set an attribute on NULL

how come?  the behaviour can obviously be explained by looking at the
source code (hardly surprisingly, because it is as it is because the
source is as it is), and referring to the NAMED property (i.e., the
sxpinfo.named field of a SEXPREC struct).  but can the *design* be
justified?  can the apparent incoherences visible above the interface be
defended? 

why should the first example above be unable to produce an empty integer
vector? 

why is it possible to set a class attribute, but not a names attribute,
on c()? 

why is it possible to set the class attribute in c() to 'integer', but
not to 'foo'? 

why are there different error messages for apparently the same problem?


vQ


[1] search the rd archives for 'surprising behaviour of names<-'
#
WK> somewhat related to a previous discussion [1] on how 'names<-' would
    WK> sometimes modify its argument in place, and sometimes produce a modified
    WK> copy without changing the original, here's another example of how it
    WK> becomes visible to the user when r makes or doesn't make a copy of an
    WK> object:

    WK> x = NULL
    WK> dput(x)
    WK> # NULL
    WK> class(x) = 'integer'
    WK> # error: invalid (NULL) left side of assignment

does not happen for me in R-2.8.1,  R-patched or newer

So you must be using your own patched version of  R ?

????


    WK> x = c()
    WK> dput(x)
    WK> # NULL
    WK> class(x) = 'integer'
    WK> dput(x)
    WK> # integer(0)

    WK> in both cases, x ends up with the value NULL (the no-value object).  in
    WK> both cases, dput explains that x is NULL.  in both cases, an attempt is
    WK> made to make x be an empty integer vector.  the first fails, because it
    WK> tries to modify NULL itself, the latter apparently does not and succeeds.

    WK> however, the following has a different pattern:

    WK> x = NULL
    WK> dput(x)
    WK> # NULL
    WK> names(x) = character(0)
    WK> # error: attempt to set an attribute on NULL

    WK> x = c()
    WK> dput(x)
    WK> # NULL
    WK> names(x) = character(0)
    WK> # error: attempt to set an attribute on NULL

    WK> and also:

    WK> x = c()
    WK> class(x) = 'integer'
    WK> # fine
    WK> class(x) = 'foo'
    WK> # error: attempt to set an attribute on NULL

    WK> how come?  the behaviour can obviously be explained by looking at the
    WK> source code (hardly surprisingly, because it is as it is because the
    WK> source is as it is), and referring to the NAMED property (i.e., the
    WK> sxpinfo.named field of a SEXPREC struct).  but can the *design* be
    WK> justified?  can the apparent incoherences visible above the interface be
    WK> defended? 

    WK> why should the first example above be unable to produce an empty integer
    WK> vector? 

    WK> why is it possible to set a class attribute, but not a names attribute,
    WK> on c()? 

    WK> why is it possible to set the class attribute in c() to 'integer', but
    WK> not to 'foo'? 

    WK> why are there different error messages for apparently the same problem?


    WK> vQ


    WK> [1] search the rd archives for 'surprising behaviour of names<-'

    WK> ______________________________________________
    WK> R-devel at r-project.org mailing list
    WK> https://stat.ethz.ch/mailman/listinfo/r-devel
#
Martin Maechler wrote:
oops, i meant to use 2.8.1 or devel for testing.  you're right, in this
example there is no error reported in > 2.8.0, but see below.
i get the error in devel.
i get the error in devel.
i get the error in devel.

it doesn't seem coherent to me:  why can i set the class, but not names
attribute on both NULL and c()?  why can i set the class attribute to
'integer', but not to 'foo', as i could on a non-empty vector:

    x = 1
    class(x) = 'foo'
    # just fine

i'd naively expect to be able to create an empty vector classed 'foo',
displayed perhaps as

    # speculation
    x = NULL
    class(x) = 'foo'
    x
    # foo(0)

or maybe as

    x
    # NULL
    # attr(, "class")
    # [1] "foo"

vQ
#

        
WK> Martin Maechler wrote:
>>>>>>> "WK" == Wacek Kusnierczyk <Waclaw.Marcin.Kusnierczyk at idi.ntnu.no>
    >>>>>>> 
    >>>>>>> 
    WK> somewhat related to a previous discussion [1] on how 'names<-' would
    WK> sometimes modify its argument in place, and sometimes produce a modified
    WK> copy without changing the original, here's another example of how it
    WK> becomes visible to the user when r makes or doesn't make a copy of an
    WK> object:
    >> 
    WK> x = NULL
    WK> dput(x)
    WK> # NULL
    WK> class(x) = 'integer'
    WK> # error: invalid (NULL) left side of assignment
    >> 
    >> does not happen for me in R-2.8.1,  R-patched or newer
    >> 
    >> So you must be using your own patched version of  R ?
    >> 

    WK> oops, i meant to use 2.8.1 or devel for testing.  you're right, in this
    WK> example there is no error reported in > 2.8.0, but see below.

ok

 [...... omitted part no longer relevant ........]

    WK> however, the following has a different pattern:
    >> 
    WK> x = NULL
    WK> dput(x)
    WK> # NULL
    WK> names(x) = character(0)
    WK> # error: attempt to set an attribute on NULL
    >> 

    WK> i get the error in devel.

Yes,  NULL is NULL is NULL !   Do read  ?NULL !   [ ;-) ]

more verbously,  all NULL objects in R are identical, or as the
help page says, there's only ``*The* NULL Object'' in R,
i.e., NULL cannot get any attributes.

    WK> x = c()
    WK> dput(x)
    WK> # NULL
    WK> names(x) = character(0)
    WK> # error: attempt to set an attribute on NULL
    >> 

    WK> i get the error in devel.

of course!  
   [I think *you* should have noticed that  NULL and c()  *are* identical]

    WK> and also:
    >> 
    WK> x = c()
    WK> class(x) = 'integer'
    WK> # fine
"fine" yes; 
here, the convention has been to change NULL into integer(0);
and no, this won't change, if you find it inconsistent.


    WK> class(x) = 'foo'
    WK> # error: attempt to set an attribute on NULL
    >> 

    WK> i get the error in devel.

No, not if you evaluate the statements above (where 'x' has
become  'integer(0)' in the mean time).

But yes, you get in something like

    x <- c();  class(x) <- "foo"

and I do agree that there's a buglet : 
The error message should be slightly more precise,
--- improvement proposals are welcome ---
but an error nontheless

    WK> it doesn't seem coherent to me:  why can i set the class, 

you cannot set it, you can *change* it.

    WK> but not names
    WK> attribute on both NULL and c()?  why can i set the class attribute to
    WK> 'integer', but not to 'foo', as i could on a non-empty vector:

    WK> x = 1
    WK> class(x) = 'foo'
    WK> # just fine

mainly because 'NULL is NULL is NULL' 
(NULL cannot have attributes)

    WK> i'd naively expect to be able to create an empty vector classed 'foo',

yes, but that expectation is wrong

    WK> displayed perhaps as

    WK> # speculation
    WK> x = NULL
    WK> class(x) = 'foo'
    WK> x
    WK> # foo(0)

    WK> or maybe as

    WK> x
    WK> # NULL
    WK> # attr(, "class")
    WK> # [1] "foo"

    WK> vQ

    WK> ______________________________________________
    WK> R-devel at r-project.org mailing list
    WK> https://stat.ethz.ch/mailman/listinfo/r-devel
#
Martin Maechler wrote:
yes, but that's not the issue.  the issue is that names(x)<- seems to
try to attach an attribute to NULL, while it could, in principle, do the
same as class(x)<-, i.e., coerce x to some type (and hence attach the
name attribute not to NULL, but to the coerced-to object).

but, as someone else explained to me behind the scenes, the matters are
a little bit, so to speak, untidy:

    x = NULL
    class(x) = 'integer'
    # just fine

    x = NULL
    attr(x, 'class') = 'integer'
    # no go

where class()<-, but not attr(,'class')<-, will try to coerce x to an
object of the storage *mode* 'integer', hence the former succeeds
(because it sets, roughly, the 'integer' class on an empty integer
vector), while the latter fails (because it tries to set the 'integer'
class on NULL itself).

what was not clear to me is not why setting a class on NULL fails here,
but why it is setting on NULL in the first place.  after all,

    x = 1
    names(x) = 'foo'

is setting names on a *copy* of 1, not on *the* 1, so why could not
class()<- create a 'copy' of NULL, i.e., an empty vector of some type
(perhaps raw, as the lowest in the hierarchy).
that's ok, this is what i'd expect in the other cases, too (modulo the
actual storage mode).
that's what i meant, must have forgotten the x = c().
terminological wars? 

btw. the class of NULL is "NULL";  why can't nullify an object by
setting its class to 'NULL'?

    x = 1
    class(x) = 'NULL'
    x
    # *not* NULL

and one more interesting example:

    x = 1:2
    class(x) = 'NULL'
    x
    # [1] 1 2
    # attr(,"class") "NULL"
    x[1]
    # 1
    x[2]
    # 2
    is.vector(x)
    # FALSE

hurray!!! apparently, i've alchemized a non-vector vector...  (you can
do it in r-devel, for that matter).
yes yes yes;  the question was, once again:  why is x still NULL?
wrt. the actual state of matters, not necessarily wrt. the ideal state
of matters ;)  (i don't insist)

vQ
#

        
WK> Martin Maechler wrote:
>> 
    >> [...... omitted part no longer relevant ........]
    >> 
    WK> however, the following has a different pattern:
    >> >> 
    WK> x = NULL
    WK> dput(x)
    WK> # NULL
    WK> names(x) = character(0)
    WK> # error: attempt to set an attribute on NULL
    >> >> 
    >> 
    WK> i get the error in devel.
    >> 
    >> Yes,  NULL is NULL is NULL !   Do read  ?NULL !   [ ;-) ]
    >> 
    >> more verbously,  all NULL objects in R are identical, or as the
    >> help page says, there's only ``*The* NULL Object'' in R,
    >> i.e., NULL cannot get any attributes.
    >> 

    WK> yes, but that's not the issue.  the issue is that names(x)<- seems to
    WK> try to attach an attribute to NULL, while it could, in principle, do the
    WK> same as class(x)<-, i.e., coerce x to some type (and hence attach the
    WK> name attribute not to NULL, but to the coerced-to object).

yes, it could;  but really, the  fact that  'class<-' works is
the exception.  The other variants (with the error message) are
the rule.

    WK> but, as someone else explained to me behind the scenes, the matters are
    WK> a little bit, so to speak, untidy:

    WK> x = NULL
    WK> class(x) = 'integer'
    WK> # just fine

    WK> x = NULL
    WK> attr(x, 'class') = 'integer'
    WK> # no go

    WK> where class()<-, but not attr(,'class')<-, will try to coerce x to an
    WK> object of the storage *mode* 'integer', hence the former succeeds
    WK> (because it sets, roughly, the 'integer' class on an empty integer
    WK> vector), while the latter fails (because it tries to set the 'integer'
    WK> class on NULL itself).

    WK> what was not clear to me is not why setting a class on NULL fails here,
    WK> but why it is setting on NULL in the first place.  after all,

    WK> x = 1
    WK> names(x) = 'foo'

    WK> is setting names on a *copy* of 1, not on *the* 1, so why could not
    WK> class()<- create a 'copy' of NULL, i.e., an empty vector of some type
    WK> (perhaps raw, as the lowest in the hierarchy).

yes, it could.  I personally don't think this would add any
value to R's behavior;  rather, for most useRs I'd think it
rather helps to get an error in such a case, than a  raw(0)
object.

Also, note (here and further below),
that Using   "class(.) <-  <className>"
is an S3 idiom   and S3 classes  ``don't really exist'', 
the "class" attribute being a useful hack,
and many of us would rather like to work and improve working
with S4 classes (& generics & methods) than to fiddle with  'class<-'.

In S4, you'd  use  setClass(.), new(.) and  setAs(.),
typically, for defining and changing classes of objects.

But maybe I have now lead you into a direction I will later
regret, 
....
when you start telling us about the perceived inconsistencies of
S4 classes, methods, etc.
BTW: If you go there, please do use  R 2.9.0 (or newer)
     exclusively.

    WK> x = c()
    WK> dput(x)
    WK> # NULL
    WK> names(x) = character(0)
    WK> # error: attempt to set an attribute on NULL
    >> >> 
    >> 
    WK> i get the error in devel.
    >> 
    >> of course!  
    >> [I think *you* should have noticed that  NULL and c()  *are* identical]
    >> 
    WK> and also:
    >> >> 
    WK> x = c()
    WK> class(x) = 'integer'
    WK> # fine
    >> "fine" yes; 
    >> here, the convention has been to change NULL into integer(0);
    >> and no, this won't change, if you find it inconsistent.
    >> 

    WK> that's ok, this is what i'd expect in the other cases, too (modulo the
    WK> actual storage mode).


    >> 
    WK> class(x) = 'foo'
    WK> # error: attempt to set an attribute on NULL
    >> >> 
    >> 
    WK> i get the error in devel.
    >> 
    >> No, not if you evaluate the statements above (where 'x' has
    >> become  'integer(0)' in the mean time).
    >> 
    >> But yes, you get in something like
    >> 
    >> x <- c();  class(x) <- "foo"
    >> 

    WK> that's what i meant, must have forgotten the x = c().

    >> and I do agree that there's a buglet : 
    >> The error message should be slightly more precise,
    >> --- improvement proposals are welcome ---
    >> but an error nontheless
    >> 
    WK> it doesn't seem coherent to me:  why can i set the class, 
    >> 
    >> you cannot set it, you can *change* it.
    >> 

    WK> terminological wars? 

    WK> btw. the class of NULL is "NULL";  why can't nullify an object by
    WK> setting its class to 'NULL'?

    WK> x = 1
    WK> class(x) = 'NULL'
    WK> x
    WK> # *not* NULL

see above {S4 / S3 / ...}; 
If you want to  "nullify", rather use
more (S-language) idiomatic calls like

    as(x, "NULL")
or  
    as.null(x)

both of which do work.

Regards,
Martin


    WK> and one more interesting example:

    WK> x = 1:2
    WK> class(x) = 'NULL'
    WK> x
    WK> # [1] 1 2
    WK> # attr(,"class") "NULL"
    WK> x[1]
    WK> # 1
    WK> x[2]
    WK> # 2
    WK> is.vector(x)
    WK> # FALSE

    WK> hurray!!! apparently, i've alchemized a non-vector vector...  (you can
    WK> do it in r-devel, for that matter).



    WK> but not names
    WK> attribute on both NULL and c()?  why can i set the class attribute to
    WK> 'integer', but not to 'foo', as i could on a non-empty vector:
    >> 
    WK> x = 1
    WK> class(x) = 'foo'
    WK> # just fine
    >> 
    >> mainly because 'NULL is NULL is NULL' 
    >> (NULL cannot have attributes)
    >> 

    WK> yes yes yes;  the question was, once again:  why is x still NULL?

    WK> i'd naively expect to be able to create an empty vector classed 'foo',
    >> 
    >> yes, but that expectation is wrong
    >> 

    WK> wrt. the actual state of matters, not necessarily wrt. the ideal state
    WK> of matters ;)  (i don't insist)

    WK> vQ
#
Martin Maechler wrote:
ok.
using latest r-devel for the most part.

i think you will probably not regret your words;  from what i've seen
already, s4 classes are the last thing i'd ever try to learn in r.  but
yes, there would certainly be lots of issues to complain about.  i'll
rather wait for s5.

regards,
vQ