2

I am trying to debug the constructor of an R6 parent class via invoking the child class.

Here is the code

library(R6)
parent <- R6Class(
  "parent",
  public=list(
    y=data.table(),
    initialize=function(){
      cat("hello","\n")
      self$y <- data.table(a=rep(1,5),b=rep("a",5))
      for(i in 1:5){
        cat("bla \n")
      }
    }
  )
)
child <- R6Class(
  "child",
  inherit=parent,
  public=list(
    initialize=function(){
      super$initialize();
    }
  )
)
parent$debug("initialize")
child$debug("initialize")
x <- child$new() ##debugging of parent consturctor doesn't work
y <- parent$new() ##debugging of parent consturctor works

So far, when constructing the child the debugger does not move through the parent constructor. The parent constructor code is executed as if debugging is switched off.

Any idea how to step through the parent constructor code without invoking the parent directly?

R Version: 4.3.2 R6 Version: 2.5.1 OS: Linux, kernel 6.7 R environment: emacs ess

1 Answer 1

1

debug() is for closures and environments

Let's look at a standard function both before and after it is being debugged.

f <- function() 1
isdebugged(f) # FALSE
.Internal(inspect(f))
# @7ffffbef4b40 03 CLOSXP g0c0 [REF(3),ATT]

# Now debug f
debug(f)
isdebugged(f) # TRUE
.Internal(inspect(f))
# @7ffffbef4b40 03 CLOSXP g0c0 [MARK,REF(7),DBG,ATT]

As R Internals sets out, R objects have a header defined in C which defines the type of object plus some other information, including the debug bit:

The debug bit is used for closures and environments. For closures it is set by debug() and unset by undebug(), and indicates that evaluations of the function should be run under the browser.

Note that once f is being debugged, the above output of .Internal(inspect(f)) indicates it has the debug bit (DBG) set.

The problem is how this works with R6 classes. parent is not an instance of an object, but rather is a class, i.e. a factory for creating instances of objects of type parent. This means that parent$initialize() is not a function.

class(parent$initialize) # NULL
typeof(parent$initialize) # NULL

So we cannot technically debug the initialize method of the parent class, but only the initialize method of an instantiated object of the parent class:

parent$debug("initialize")
isdebugged(parent$initialize)
# Error in isdebugged(parent$initialize) : argument must be a function
.Internal(inspect(parent$initialize)) # No DBG bit
# @7ffff7913220 00 NILSXP g1c0 [MARK,REF(65535)]

Now you might think that you can debug parent$new() because it appears to be a function:

class(parent$new) # function
typeof(parent$new) # closure

However, the only benefit to it being a closure is that isdebugged() will not raise an error, but instead tell you that R will not allow you to set the debug flag.

parent$debug("new")
isdebugged(parent$new)
# [1] FALSE
.Internal(inspect(parent$new)) # No DBG bit
# @7ffff9c5cd08 03 CLOSXP g1c0 [MARK,REF(2),gp=0x40]

Why debug works when you instantiate an object of type parent

If you run parent$debug("initialize") then any the initialize method of any object of class parent will be debugged:

x  <- parent$new()
isdebugged(x$initialize) # TRUE

This means that when you create x you will enter the debug browser because the initialize method is called on x, rather than because parent$initialize is debugged. However, that's no use to you here as you are creating an object of type child.

How to debug() the super class

However, it is possible to address this from within the debug browser. That is because super is not seen as a class, but in fact an environment and the initialize method is a function:

child$debug("initialize")
x <- child$new() 
Browse[2]> class(parent) # "R6ClassGenerator"
Browse[2]> class(super) # "environment"
Browse[2]> class(super$initialize) # "function"

We can debug environments and functions. So once we're in the browser, we can enter the debug instruction relating to super rather than parent. So call (the debugged) child$new() and then before you press enter in the browser:

Browse[2]> debug(super$initialize)

You'll see that this lets you step through into the next level, Browse[3]:

debug at #6: super$initialize()
Browse[2]>
debugging in: super$initialize()
debug at #5: {
    cat("hello", "\n")
    self$y <- data.table(a = rep(1, 5), b = rep("a", 5))
    for (i in 1:5) {
        cat("bla \n")
    }
}
Browse[3]>

If you are going to be doing a lot of debugging and don't want to enter this every time, you could amend your initialize() function for the child class to include a debug_parent flag:

child <- R6Class(
    "child",
    inherit = parent,
    public = list(
        initialize = function(debug_parent = FALSE) {
            if (debug_parent) debug(super$initialize)
            super$initialize()
        }
    )
)

You could then call this as follows:

x <- child$new(debug_parent = TRUE) 

You will want to remember to take this out when you've finished, but it avoids the need to ask the browser to debug the parent every time you call the child.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.