Skip to content

Unexpected behavior of identical() with language objects

7 messages · Winston Chang, Joshua Ulrich, Lorenz, David +3 more

#
I ran into this and found the result very surprising:

identical( quote({ a }),  quote({ a }) )
# FALSE

It seems related to curly braces. For example, parens work fine:
identical( quote(( a )),  quote(( a )) )
# TRUE

Is this expected behavior? I can't seem to find anything in the help
for identical that relates to this.

-Winston
#
On Wed, Oct 29, 2014 at 3:26 PM, Winston Chang <winstonchang1 at gmail.com> wrote:
It's not in ?identical, but ?Paren gives you some pointers.
str(quote((a))) and str(quote({a})) are also informative.

  
    
#
Fascinating! I tried the comparisons with all.equal(), expecting a
description of the difference, but TRUE was returned in both cases.
Dave


On Wed, Oct 29, 2014 at 3:26 PM, Winston Chang <winstonchang1 at gmail.com>
wrote:

  
  
#
Yes, looks like srcrefs are to blame:

x <- quote({ a })
y <- quote({ a })

identical(x, y)
# [1] FALSE

attr(x, "srcref") <- NULL
attr(x, "srcfile") <- NULL
attr(x, "wholeSrcref") <- NULL

attr(y, "srcref") <- NULL
attr(y, "srcfile") <- NULL
attr(y, "wholeSrcref") <- NULL
identical(x, y)
# [1] TRUE

Maybe identical() needs an ignore.srcref option? Normally when
comparing expressions or functions, you want to compare the code, not
it's textual representation.

Hadley
#
Ah, I was using identical() to compare two function bodies. It returns
FALSE even when you remove srcrefs from the body:

f1 <- function(x) {
  if (TRUE) { x }
}
f2 <- function(x) {
  if (TRUE) { x }
}
f1b <- body(f1)
f2b <- body(f2)
attributes(f1b) <- NULL
attributes(f2b) <- NULL

# The bodies look the same with str()
str(f1b)
#  language {  if (TRUE) {; x; } }
str(f2b)
#  language {  if (TRUE) {; x; } }

identical(f1b, f2b)
# FALSE



What I didn't realize was that the curly brace inside the body also
independently captures srcrefs, but this isn't printed with str(f1b).
However, str() on a more targeted part of the object reveals them:
str(f1b[[2]][[3]])
# length 2 {  x }
#  - attr(*, "srcref")=List of 2
#   ..$ :Class 'srcref'  atomic [1:8] 2 13 2 13 13 13 2 2
#   .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile'
<environment: 0x452b2c0>
#   ..$ :Class 'srcref'  atomic [1:8] 2 15 2 15 15 15 2 2
#   .. .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile'
<environment: 0x452b2c0>
#  - attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile' <environment:
0x452b2c0>
#  - attr(*, "wholeSrcref")=Class 'srcref'  atomic [1:8] 1 0 2 17 0 17 1 2
#   .. ..- attr(*, "srcfile")=Classes 'srcfilecopy', 'srcfile'
<environment: 0x452b2c0>

-Winston
On Wed, Oct 29, 2014 at 3:46 PM, Hadley Wickham <h.wickham at gmail.com> wrote:
#
[See below for full email trail-- Outlook has beaten me into submission]
What a great gotcha!

Seems to me it would be better to leave 'identical' alone and have a wrapper that you can call to strip any srcrefs. There is already 'utils::removeSource' but it doesn't work as-is with non-functions. The 5-minute hack below solves the particular example, but may well fall over with other cases--- not tested.

(This sort of thing reinforces my own feelings about 'srcref' as opposed to nice simple 'source'...)

Mark Bravington
CSIRO/Marine Lab/Hobart/Tas 7000/Australia

rmsrc <- function (fn) {
# based on utils::removeSource
    is.fun <- is.function( fn)
    if( (!is.fun && !is.language(fn)) || is.primitive(fn)) {
return(fn)
    }
    
    attr(fn, "source") <- NULL
    attr(fn, "srcref") <- NULL

    if( !is.fun) {
      fn <- as.function( list( fn))
    }

    attr(body(fn), "wholeSrcref") <- NULL
    attr(body(fn), "srcfile") <- NULL
    recurse <- function(part) {
        attr(part, "srcref") <- NULL
        if (is.language(part) && is.recursive(part)) {
            for (i in seq_along(part)) part[[i]] <- recurse(part[[i]])
        }
        part
    }
    body(fn) <- recurse(body(fn))
    
    if( !is.fun) {
return( body( fn))
    } else {
return( fn)
    }
}
#> identical( rmsrc( quote({a})), rmsrc( quote({a})))
#[1] TRUE
#
On 29/10/2014, 6:22 PM, Mark.Bravington at csiro.au wrote:
Sounds like utils::removeSource may need to be fixed, but until today
I'd never heard any complaints about it.
Nice simple 'source' can't do what srcrefs can do, for example report on
locations during debugging, or when run-time errors occur.

Duncan Murdoch