Override non-default method and avoid recursion

100 Views Asked by At

What programming principles can help me avoid recursion when using the generic function system to dispatch a modified method that calls the original method?

Background

I'm not satisfied with the behavior of bit64::as.integer64.character() for reasons discussed here, so I've written a workaround function, which calls bit64::as.integer64() and then makes some changes to the output:

test_string <- c('1234','5678','', 'Help me Stack Overflow')

# illustration of undesired behavior:
library(bit64)
as.integer64(test_string) # returns int64 [1] 1234 5678 0 <NA> without warning

# workaround:    
charToInt64 <- function(s){
  stopifnot( is.character(s) )
  x <- bit64::as.integer64(s)
  # as.integer64("") unexpectedly returns zero without warning.  
  # Overwrite this result to return NA without warning, similar to base as.integer("")
  x[s==""] <- NA_integer64_
  # as.integer64("ABC") unexpectedly returns zero without warning.
  # Overwrite this result to return NA with same coercion warning as base as.integer("ABC")
  bad_strings <- grepl('\\D',s)
  if( any(bad_strings) ){
    warning('NAs introduced by coercion')
    x[bad_strings] <- NA_integer64_  
  }
  x
}

# Demo of workaround -- this behavior mimics base as.integer():
charToInt64(test_string) # returns int64 [1] 1234 5678 <NA> <NA> with warning
charToInt64(head(test_string,-1)) # returns int64 [1] 1234 5678 <NA> without warning

So far, so good.

Next, as a learning exercise, I'm trying to override the method as.integer64.character() with this workaround. (I apologize if I'm misusing any OOP terminology in this post, please correct me.)

What I tried

First attempt (doesn't work, infinite recursion)

Naively, I reasoned that since I'm making the namespace explicit when calling the generic function bit64::as.integer64() in my workaround function, I should be able to define a method with the same name, but in the global environment that I can call without kicking off infinite recursion.

as.integer64.character <- charToInt64 # override method in global environment
as.integer64(test_string) # Error: C stack usage  19922992 is too close to the limit

Second attempt (works, but I don't understand why)

Replacing the call to the generic function bit64::as.integer64(s) with a call to the string method bit64::as.integer64.character(s) solves the problem.

charToInt64v2 <- function(s){
  stopifnot( is.character(s) )
  x <- bit64::as.integer64.character(s) # this is the only change
  x[s==""] <- NA_integer64_
  bad_strings <- grepl('\\D',s)
  if( any(bad_strings) ){
    warning('NAs introduced by coercion')
    x[bad_strings] <- NA_integer64_  
  }
  x
}
as.integer64.character <- charToInt64v2 
as.integer64(test_string) # works as desired

Recap of question

Why does the second attempt work when the first fails? What is it about namespaces and/or dispatch that I don't understand?

0

There are 0 best solutions below