S3 - delayed method registration in R - r

I have an R-package ('mill') with a 'suggest' ('hlt') where I would like to register a new class for an S3 method in 'hlt'. Because the 'hlt' package is a suggest I followed the advise of Hadley and copied a function in 'mill' that registers my new mill::html.pdf_diff_df() function but not until after loading the 'hlt' package:
https://github.com/r-lib/vctrs/blob/master/R/register-s3.R
The problem I encounter is that when I load 'mill' and call the html() method on the new class ('pdf_diff_df') defined in the 'mill' package I just get the error:
html(df)
Error in html(df) : could not find function "html"
I would, of course, like to give the warning that one should load the 'hlt' package at that point, otherwise the user doesn't understand the problem. Any ideas? The source in 'mill' of this new function is here:
https://github.com/SVA-SE/mill/blob/136f372f88d794bb6149922c24dd9a4f731e4c7e/R/images.R#L195-L206

If I understand the situation correctly, hlt exports html(), but you don't want a fixed dependency on hlt, so you can't import html from hlt.
In that case, simply use a fully qualified call to it, i.e. hlt::html(...).
Since hlt is only in Suggests, this should only be called conditional on requireNamespace('hlt') returning TRUE.
Edited to add:
If you want a user to be able to call html(...), you have to export it from your package. This is hard, because it is exported from the hlt package, and if the two packages export different versions, you'll get warnings about one hiding the other.
As far as I know there isn't a nice solution to this, but really, it's not your problem. If a user hasn't attached hlt, they have no reason to expect html(...) to do anything. If they have, it will just work.
If you really want to help out the user without generating new messages, you could export your own function with a different name, something like
html2 <- function(...) {
if (requireNamespace("hlt"))
hlt::html(...)
else
warning("Suggested package 'hlt' must be installed for this to work.")
}

Related

Using trace on a hidden function to edit source code in R

For a package I am using, I would like to fix part of the code. I have downloaded the "package source" from CRAN and have narrowed down where the problem is. I would like to edit the problem function in RStudio. I've tried using trace, but I am unable to call the desired function. The function I want to call is a helper function (not in the documentation) to the main function (which is in the documentation). Is there a way to edit the functions not in the documentation and used in the implementation?
If it helps, the package I am using is called RecordLinkage. I would like to change the function .toFF which is called by RLBigDataDedup and is in the file RLBigData-classes.r.
Now that I've established the problem by looking at the source code, I now want to edit it in RStudio so that I can run it when using the package... The best I can do is looking at RLBigDataDedup in RStudio, but I can't find the .toFF that is used within it.
Thats not working because the hidden function .toFF is not known in the global env because it is hidden. If you want to edit/debug hidden function you have to specify the where argument in trace() with the corresponding function. In your case it would be RLBigDataLinkage.
Regarding to the docs of trace, it is stated:
For “hidden” functions such as S3 methods in a namespace, where = *
typically needs to be specified as well
So for your answer this will work:
trace(".toFF", edit=T, where = RLBigDataLinkage)
try debug(.toFF) or debug(function_to_calls_.toFF) This will open the debug tool when the function is called.

how to use utils::globalVariables

Following your recommendations (or trying to do it, at least), I have tried some options, but the problem remains, so there must be something I am missing.
I have included a more complete code
setwd("C:/naapp")
#' #import utils
#' #import devtools
I have tried with and without using suppressForeignCheck
if(getRversion() >= "2.15.1"){
utils::globalVariables(c("eleven"))
utils::suppressForeignCheck(c("eleven"))
}
myFunctionSum <- function(X){print(X+eleven)}
myFunctionMul <- function(X){print(X*eleven)}
myFunction11 <- function(X){
assign("eleven",11,envir=environment(myFunctionMul))
}
maybe I should use a particular environment?
package.skeleton(name = "myPack11", list=ls(),
path = "C:/naapp", force = TRUE,
code_files = character())
I remove the "man" directory from the directory myPack11,
otherwise I would get an error because the help files are empty.
I add the imports utils, and devtools to the descrption
Then I run check
devtools::check("myPack11")
And I still get this note
#checking R code for possible problems ... NOTE
#myFunctionMul: no visible binding for global variable 'eleven'
#myFunctionSum: no visible binding for global variable 'eleven'
#Undefined global functions or variables:eleven
I have tried also to make an enviroment, combining Tomas Kalibera's suggetion and an example I found in the Internet.
myEnvir <- new.env()
myEnvir$eleven <- 11
etc
In this case, I get the same note, but with "myEnvir", instead of "eleven"
First version of the question
I trying to use "globalVariables" from the package utils. I am building an interface in R and I am planning to submit to CRAN. This is my first time, so, sorry if the question is very basic.I have read the help and I have tried to find examples, but I still don't know how to use it.
I have made a little silly example to ilustrate my question, which is:
Where do I have to place this line exactly?:
if(getRversion() >= "2.15.1"){utils::globalVariables("eleven")}
My example has three functions. myFunction11 creates the global variable "eleven" and the other two functions manipulate it. In my real code, I cannot use arguments in the functions that are called by means of a button. Consider that this is just a silly example to learn how to use globalVariables (to avoid binding notes).
myFunction11 <- function(){
assign("eleven",11,envir=environment(myFunctionSum))
}
myFunctionSum <- function(X){
print(X+eleven)
}
myFunctionMul <- function(X){
print(X*eleven)
}
Thank you in advance
I thought that the file globals.R would be automatically generated when using globalsVariables. The problem was that I needed to create the package skeleton, then create the file globals.R, add it to the R directory in the package and check the package.
So, I needed to place this in a different file:
#' #import utils
utils::globalVariables(c("eleven"))
and save it
The documentation clearly says:
## In the same source file (to remind you that you did it) add:
if(getRversion() >= "2.15.1") utils::globalVariables(c(".obj1", "obj2"))
so put it in the same source file as your functions. It can go in any of your R source files, but the comment above recommends you put it close to your code. Looking at a bunch of github packages reveals another common pattern is to have a globals.R function with it in, but this is probably a bad idea. If you later remove the global from your package but neglect to update globals.R you could mask a problem. Putting it right close to the functions that use it will hopefully remind you when you edit those functions.
Make sure you put it outside any function definitions in the file, or it won't get seen.
You cannot modify bindings in a package namespace once the package is loaded (and namespace sealed, and bindings locked). The check tool helps you to spot violations of this restriction, so you find out about the problem when checking the package rather than while running it. globalVariables is just a call to silence check when looking for these violations, which is undesirable in almost all cases. If you really need mutable state in a package, you can create a new environment (using new.env) and bind it to an (unexported) "global" variable in your namespace. This binding will be locked, but this is ok, because in R you can change an environment in place (add/remove elements, effectively modifying the elements).
The best situation is however when you can keep all mutable state in user objects (passed in as arguments into functions, and their modified versions returned as output values of functions).

R: How do I add an extra function to a package?

I would like to add an idiosyncratically modified function to package written by someone else, with an R Script, i.e. just for the session, not permanently. The specific example is, let's say, bls_map_county2() added to the blscrapeR package. bls_map_county2 is just a copy of the bls_map_county() function with an added ... argument, for purposes of changing a few of the map drawing parameters. I have not yet inserted the additional parameters. Running the function as-is, I get the error:
Error in BLS_map_county(map_data = df, fill_rate = "unemployed_rate", :
could not find function "geom_map"
I assume this is because my function does not point to the blscrapeR namespace. How do I assign my function to the (installed, loaded) blscrapeR namespace, and is there anything else I need to do to let it access whatever machinery from the package it requires?
When I am hacking on a function in a particular package that in turn calls other functions I often use this form after the definition:
mod_func <- function( args) {body hacked}
environment(mod_func) <- environment(old_func)
But I think the function you might really want is assignInNamespace. These methods will allow the access to non-exported functions in loaded packages. They will not however succeed if the package is not loaded. So you may want to have a stopifnot() check surrounding require(pkgname).
There are two parts to this answer - first a generic answer to your question, and 2nd a specific answer for the particular function that you reference, in which the problem is something slightly different.
1) generic solution to accessing internal functions when you edit a package function
You should already have access to the package namespace, since you loaded it, so it is only the unexported functions that will give you issues.
I usually just prepend the package name with the ::: operator to the non exported functions. I.e., find every instance of a call to some_internal_function(), and replace it with PackageName:::some_internal_function(). If there are several different internal functions called within the function you are editing, you may need to do this a few times for each of the offending function calls.
The help page for ::: does contain these warnings
Beware -- use ':::' at your own risk!
and
It is typically a design mistake to use ::: in your code since the
corresponding object has probably been kept internal for a good
reason. Consider contacting the package maintainer if you feel the
need to access the object for anything but mere inspection.
But for what you are doing, in terms of temporarily hacking another function from the same package for your own use, these warnings should be safe to ignore (at you own risk, of course - as it says in the manual)
2) In the case of blscrapeR ::bls_map_county()
The offending line in this case is
ggplot2::ggplot() + geom_map(...
in which the package writers have specified the ggplot2 namespace for ggplot(), but forgotten to do so for geom_map() which is also part of ggplot2 (and not an internal function in blscrapeR ).
In this case, just load ggplot2, and you should be good to go.
You may also consider contacting the package maintainer to inform them of this error.

R package namespace issue using data() -- data set not found

I've hit an issue trying to import a package (namely, 'robfilter') inside one of my own packages. One of its methods that I am trying to use, adore.filter, is failing at this line:
data(critvals)
With error 'data set 'critvals' not found'.
The function works fine if I load the library via require(robfilter). However, this means that in order to use my custom package which calls adore.filter, I will have to load my own package, and then load robfilter. Not a huge problem but slightly annoying.
I'm not sure if the problem is that there is an extra step I need to do in order to make critvals visible within my package, or if perhaps there is something the package author needed to do (and hasn't done) to add critvals to its package namespace; there is no sign of 'critvals' in the robfilter NAMESPACE file. I haven't encountered this issue before and don't really understand how the use of data() inside a package is supposed to work.
There are two solutions as far as I know:
Either ask the robfilter Maintainer to put the data needed by robfiler in the internal data file of robfilter. (R/sysdata.rda)
Or make your package Depends on robfilter
So it works if you put robfilter in the depends section of your description file. But in my case (both are my packages), I was trying to avoid the Depends solution as it loads the imported package and also any other package will need to depend ont its imported package... See my question is quite a duplicate of yours but not in the same context.

Dynamically Generate Reference Classes

I'm attempting to generate reference classes within an R package on the fly, and it's proving to be fairly difficult. Here are the approaches I've taken and problems I've run into:
I'm creating a package in which I hope to be able to dynamically read in a schema and automatically generate an associated reference class (think SOAP). Of course, this means I won't be able to define my reference classes before-hand in the package sources.
I initially attempted to create a new class using a simple:
myClass <- setRefClass("NewClassName", fields=list(fieldA="character"))
which, of course, works fine when executed interactively, but when included in the package sources, I get a locked binding error. From my reading, it looks like this occurs because when running interactively, the class information is being stored in the global environment, which is not locked, while my package's base environment is locked.
I then found a thread that suggested using something to the effect of:
myClass <- setRefClass("NewClassName", fields=list(fieldA="character"), where=globalenv())
This actually crashed R/Studio when I tried to build the package, so I don't have a log of the error it generated, unfortunately, but it certainly didn't work.
Next I tried creating a new environment within my package which I could use to store these reference classes. So I added a .classEnv <- new.env() line in my package sources (not inside of any function) and then attempted to use this class when creating a new reference class:
myClass <- setRefClass("NewClassName", fields=list(fieldA="character"), where=.classEnv)
This actually seemed to work OK, but generates the following warning:
> myClass <- setRefClass("NewClassName", where=.classEnv)
Warning message:
In getPackageName(where) :
Created a package name, ‘2013-04-23 10:19:14’, when none found
So, for some reason, methods::getPackageName() isn't able to pick up which package my new environment is in?
Is there a way to create my new environment differently so that getPackageName() can properly recognize the package? Can I add some feature which allows me to help getPackageName() detect the package? Will this even work if I can deal with the warning, or am I misusing reference classes by trying to create them dynamically?
To get the conversation going, I found that getpackageName stores the package name in a hidden .packageName variable in the specified environment.
So you can actually get around the warning with
assign(".packageName", "MyPkg", envir=.classEnv)
myClass <- setRefClass("NewClassName", fields=classFields, where=.classEnv)
which resolves the warning, but the documentation says not to trust the .packageName variable indefinitely, and I still feel like I'm hacking this in and may be misunderstanding something important about reference classes and their relationship to environments.
Full details from documentation:
Package names are normally installed during loading of the package, by the INSTALL script or by the library function. (Currently, the name is stored as the object .packageName but don't trust this for the future.)
Edit:
After reading a little further, the setPackageName method may be a more reliable way to set the package name for the environment. Per the docs:
setPackageName can be used to establish a package name in an environment that would otherwise not have one. This allows you to create classes and/or methods in an arbitrary environment, but it is usually preferable to create packages by the standard R programming tools (package.skeleton, etc.)
So it looks like one valid solution would be the following:
setPackageName("MyPkg", .classEnv)
myClass <- setRefClass("NewClassName", fields=classFields, where=.classEnv)
That eliminates the warning message and doesn't rely on anything that's documented as unstable. I'm still not clear why it's necessary, but...

Resources