On Recursion - Part 3

Master R

By Guangming Lang Comment

Base R has a function get() that searches for a given name over the environment stack and returns its value after finding it. For example, we can use it like this.

x = 9
get("x")
## [1] 9
get("mean") # inherits is set to TRUE by default
## function (x, ...) 
## UseMethod("mean")
## <bytecode: 0x7f99b373e028>
## <environment: namespace:base>
get("mean", inherits = FALSE)
## Error in get("mean", inherits = FALSE): object 'mean' not found
mean = function() "fake mean"
get("mean")
## function() "fake mean"

If you don’t understand why they returned the values they did, you can learn how environment works by reading Hadley’s book.

We can write our own version of get() using recursion. First we write a helper function that works the same as get(name, inherits = T).

get_helper = function(name, env = parent.frame()) {
        # Returns the value that name binds to. Looks for name in the given 
        #       environment and all its parents.
        # name: string, name of an object
        # env : environment object where the search begins. Default value is 
        #       the global environment
        
        if (identical(env, emptyenv())) { # base case
                stop("object '", name, "'", " not found", call. = F)
        } else if (exists(name, envir = env, inherits = F)) { # success case
                env[[name]]
        } else { # recursive case
                get_helper(name, parent.env(env))
        }
}

get_helper("x")
## [1] 9
get("mean")
## function() "fake mean"

Next we can easily extend it to a more general version that takes an additional parameter inherits.

get_gmlang = function(name, env=parent.frame(), inherits = T) {
        # Returns the value that name binds to. If inherits = T, looks for name
        #       in the given environment and all its parents. Otherwise, looks
        #       for name only in the given environment.
        #
        # name: string, name of an object
        # env : environment object where the search begins. Default value is 
        #       the global environment
        # inherits: logical
        
        if (inherits) { 
                get_helper(name, env)
        } else if (exists(name, envir = env, inherits = F)) {
                env[[name]]
        } else {
                stop("object '", name, "'", " not found", call. = F)
        }
}

e = new.env()
e$z = 100
get_gmlang("x", env=e, inherits = T)
## [1] 9
get_gmlang("x", env=e, inherits = F)
## Error: object 'x' not found
get_gmlang("z", env=e)
## [1] 100
get_gmlang("z") # note: global environment is the parent of e
## Error: object 'z' not found

Moreover, we can easily extend get_helper() to a function fget_helper() that finds only function objects.

fget_helper = function(name, env = parent.frame()) {
        # Returns the value that name binds to when name binds to a function. 
        #       Looks for name in the given environment and all its parents.
        #
        # name: string, name of an object
        # env : environment object where the search begins. Default value is 
        #       the global environment

        if (identical(env, emptyenv())) { # base case
                stop("Can't find ", name, " as a function object", call. = F)
        } else if (class(env[[name]]) == "function") { # success case
                # if name not in env, env[[name]] returns NULL with class "NULL"
                env[[name]]
        } else { # fail case
                fget_helper(name, parent.env(env))
        }
}

fget_helper("x")
## Error: Can't find x as a function object
fget_helper("mean")
## function() "fake mean"
rm("mean")
fget_helper("mean")
## function (x, ...) 
## UseMethod("mean")
## <bytecode: 0x7f99b373e028>
## <environment: namespace:base>

If you enjoyed this post, get updates. It's FREE