Rcpp: Environment.get() version throwing an exception instead of returning NULL

Created on 26 Oct 2016  路  8Comments  路  Source: RcppCore/Rcpp

In R one can search an environment without inheritance and find names that have NULL assigned:
get( "varName", envir=enEnv, inherits=FALSE)

Rcpp::Environement::get method returns NULL if the name is not assigned. Hence it cannot distinguish between a name bound to NULL or a nonexisting name.

Rcpp::Environement::find methods throws an error if the name is not bound. Hence, it is suitable to distinguish unbound versus NULL-bound names. However it corresponds to inherits=TRUE.

Hence, a method in the Environment class would be welcomed that throws an exception instead of returning NULL but only searches the environment without its parents. Name suggestion: SEXP getThrows( const std::string& name) const

Most helpful comment

The other alternate is to return the sentinel R value R_UnboundValue, and instruct users to check against that value.

All 8 comments

Good report. Would you mind just putting together a five or ten liner we can sourceCpp() to have concrete behaviour? A working example of what exactly you don R and what to do from C++ would also help.

The NULL as 'not here' as well explicit assigned NULL is a little unfortunate. We can probably extend the API to add a bool isSet(const std::string& varname) or something like that.

The other alternate is to return the sentinel R value R_UnboundValue, and instruct users to check against that value.

Here the code with concrete behaviour.

get_cpp <- cxxfunction(signature(name_x="character", envir_x="environment"),    
        plugin="Rcpp"
        ,body='
                std::string name = as<std::string>(name_x);
                Environment envir(envir_x);
                return envir.get(name);
                ')

find_cpp <- cxxfunction(signature(name_x="character", envir_x="environment"),   
        plugin="Rcpp"
        ,body='
                std::string name = as<std::string>(name_x);
                Environment envir(envir_x);
                return envir.find(name);
                ')

c <- "cParent"      # value in parent environment
a <- "aParent"
env <- new.env()
env$a <- NULL

stopifnot(  get("a", envir=env, inherits=FALSE) == NULL )
try(  get("c", envir=env, inherits=FALSE)  )    # expect error
stopifnot(  get("a", envir=env, inherits=TRUE) == NULL )

get_cpp("a", env)   # NULL wanted behaviour
get_cpp("nonExisting", env) # NULL instead of notification that it is not existing
get_cpp("c", env)   # NULL instead of notification that it is not existing

find_cpp("a", env)  # NULL wanted behaviour
try(find_cpp("nonExisting", env))  # error: wanted behaviour 
find_cpp("c", env)  # "cParent": instead of notification 

Same thing using Rcpp Attributes which makes it a lot more concide.

Use sourceCpp("nameOfTheFile.cpp") to _build and run the R illustration_ in one step.

#include <Rcpp.h>

using namespace Rcpp;

// [[Rcpp::export]]
SEXP get_cpp(std::string name, Environment envir) {
  return envir.get(name);
}

// [[Rcpp::export]]
SEXP find_cpp(std::string name, Environment envir) {   
  return envir.find(name);
}

/*** R
c <- "cParent"      # value in parent environment
a <- "aParent"
env <- new.env()
env$a <- NULL

stopifnot(  get("a", envir=env, inherits=FALSE) == NULL )
try(  get("c", envir=env, inherits=FALSE)  )    # expect error
stopifnot(  get("a", envir=env, inherits=TRUE) == NULL )

get_cpp("a", env)   # NULL wanted behaviour
get_cpp("nonExisting", env) # NULL instead of notification that it is not existing
get_cpp("c", env)   # NULL instead of notification that it is not existing

find_cpp("a", env)  # NULL wanted behaviour
try(find_cpp("nonExisting", env))  # error: wanted behaviour 
find_cpp("c", env)  # "cParent": instead of notification 
*/

Edit: Made the return value a SEXP as we do not know what is being looked up.

I am not familiar with R Internals. But being able to check the return value with an if-statement (as I understood @kevinushey) in order to distinguish unbound versus NULL values would probably be better and faster than using try-catch statements. An example of how to do this check should then be provided in the function documentation.

So, harvesting from Environment get the isSet is straightforward with the exception of what to do in regard to promises. Should the promise be evaluated and if there is a result then the value is set? Or is just the presence of the promise sufficient enough to trigger a set condition?

I've opted to go along the lines of the later here:

bool isSet(const std::string& name) const {
    SEXP env = Storage::get__() ;
    SEXP nameSym = Rf_install(name.c_str());
    SEXP res = Rf_findVarInFrame( env, nameSym ) ;

    if( res == R_UnboundValue ) return false; // not found in environment

    return true; // object _might_ be a promise in this case.
}
> is_cpp("a", env)   # exists
[1] TRUE

> is_cpp("nonExisting", env) #  not existing
[1] FALSE

> is_cpp("c", env)   #  not existing
[1] FALSE

@progtw, sorry for not seeing this earlier, but the above function is not necessary as Rcpp already has one that provides the same functionality via exists.

Example:

// [[Rcpp::export]]
bool exists_cpp(std::string name, Environment envir) {   
  return envir.exists(name);
}

Only in the case where a promise object needs to be correctly evaluated is a new function (or a parameter on exists) required.

@eddelbuettel: This issue can be closed out as the functionality already exists in the member function exists.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ivan-krukov picture ivan-krukov  路  8Comments

zhiruiwang picture zhiruiwang  路  6Comments

eddelbuettel picture eddelbuettel  路  3Comments

nettoyoussef picture nettoyoussef  路  6Comments

nathan-russell picture nathan-russell  路  3Comments