0

TL;DR

  • R Package development;
  • Not exported utility function results in Error could not find function in exported one.
  • Basically the same problem described here
  • However the solution provided there using ::: does not solve it for me, because as a consequence then R-CMD-Check throws a warning for using :::

Full Story:

I am developing a small package of R functions let's call the package foo. I recently ran into max capacities for imports (> 20), thats why I reduced them and wrote for some packages my own work around utility functions. E.g., I removed the readr package from my DESCRIPTION which I just used for the parse_number() function.

Now I created a utility.R file. The two functions in this file are:

  • clean_names()
  • number_parse()

These are not export via Roxygen. The first function (clean_names()) works and can be called from expored / public functions.

The Problem:

However, the second function included in my utility file number_parse() is not found from the exported functions. When I change my call to from number_parse() to foo:::number_parse() it works. However then the R-CMD Check fails with a warning for the use of :::. Also if I make the second function external (roxygen @export) it works aswell. However I would prefer to leave these cheap utility functions private as they might change, get decaprecated or removed in the near future

Explanation in Pseudo Code

utility.R

This Works when called in function below, due to the addition of @export. However then it is not an internal function anymore

#' number_parse
#' @param s character or vector of characters, to be parsed to a number
#' @export
#' @NoRd
number_parse <- function(s) {
  some functionality
  return(out)
}

Code for the internal utlity function

So here is my naive try a the number_parse function. The problem might be related to the defintion of a function inside another function (nested functions). However I just tired moving the inner parse_oneNumber() out of the number_parse() it did not solve the issue sadly.

#' number_parse
#' @param s character or vector of characters, to be parsed to a number
#' @NoRd
number_parse <- function(s) {
  parse_oneNumber <- function(s) {
    if (length(s) == 0) return(NA)
    if(!is.na(s)){
      # remove all non number related characters
      s <- gsub("[^0-9.-]", "", s)
      # Only use the first decimal point (the digits before and after)
      s <- unlist(strsplit(s,"\\."))
      # Only use digits after the -
      s <- gsub(".*-","-",s)
      # Remove now empty strings due to multiple .
      s <- s[s!=""]
      # If decimal place was present concat to one string
      if (length(s)>1) s <- paste0(s[1],".",s[2])
      # convert
      s <- as.numeric(s)
      return(s)
    }else{
      return(NA)
    }  
  }

  # Check if multiple strings were given
  if (length(s)>1) {
    # if more than one string is given, iterate over strings
    out <-  c()
    for(w in s){
      tmp <- parse_oneNumber(w)
      # if empty is returned (no number parseable, append NA to retain dimensions)
      if(length(tmp) != 0 ) out <- c(out,tmp) else out <- c(out,NA)
    }
  } else {
    tmp <-  parse_oneNumber(s)
    if(length(tmp) != 0 ) out <- tmp else out <- NA
  }
  return(out)
}

Update:
Separation in a new second utility2.R file did not solve it, moving the parse_oneNumber() function outside the number_parse function did also not solve it (see below).

#' parse_oneNumber
#' @param s one string to be parsed to a number
#' @NoRd
parse_oneNumber <- function(s) {
  # Check given argument, else return NA
  if (length(s) == 0) return(NA)
  if (is.na(s)) return(NA)

  # remove all non number related characters
  s <- gsub("[^0-9.-]", "", s)
  # Only use the first decimal point (the digits before and after)
  s <- unlist(strsplit(s,"\\."))
  # Only use digits after the -
  s <- gsub(".*-","-",s)
  # Remove now empty strings due to multiple .
  s <- s[s!=""]
  # If decimal place was present concat to one string
  if (length(s)>1) s <- paste0(s[1],".",s[2])
  # convert
  s <- as.numeric(s)
  return(s)
}

#' number_parse
#' @param s character or vector of characters, to be parsed to a number
#' @NoRd
number_parse <- function(s) {
  # Check if multiple strings were given
  if (length(s)>1) {
    # if more than one string is given, iterate over strings
    out <-  c()
    for(w in s){
      tmp <- parse_oneNumber(w)
      # if empty is returned (no number parseable, append NA to retain dimensions)
      if(length(tmp) != 0 ) out <- c(out,tmp) else out <- c(out,NA)
    }
  } else {
    tmp <-  parse_oneNumber(s)
    if(length(tmp) != 0 ) out <- tmp else out <- NA
  }
  return(out)
}

I am getting to a point, where I guess I will just give up and parse the number_parse function.

Björn
  • 1,610
  • 2
  • 17
  • 37
  • 2
    This looks like a problem with how your package is collated. You can use Roxygen2 to sort this out for you. Ensure you have an `@include utility.R` in the exported function that uses `number_parse`. Roxygen will generate a 'Collate:` field in your DESCRIPTION file that ensures the files are read in the correct order. – Allan Cameron Mar 18 '22 at 21:20
  • Thank you allan, I will check this out. – Björn Mar 18 '22 at 21:23
  • This sounded totally plausible however it sadly did not solve the issue. I added the roxygen `@include` at the beginning uf my roxygen docstrings. R CMD did then add the `Collate` field in my DESCRIPTION as you said. The order there seems correct. First `utility.R` then `someExported.R` however it still fails with the error `could not find function "number_parse"` – Björn Mar 18 '22 at 21:30
  • It might be due to the fact that I used a nested function, inside the internal function? – Björn Mar 18 '22 at 21:37
  • I'm not sure what you mean about a nested function. Perhaps show us some code? – Allan Cameron Mar 18 '22 at 21:38
  • Maybe I could add an `@importFrom foo number_parse` at the external function, to solve it? – Björn Mar 18 '22 at 21:47
  • 3
    I strongly suspect that the actual reason is a simple typo, nothing else. The fact that it works via `foo:::numberParse` seems to indicate that this is unlikely, but stranger things have happened. Maybe explicit qualification causes another version of your development package to be loaded. Either way this issue is impossible to debug from a distance. Reduce the problem by deleting more and more irrelevant code until the problem vanishes. Then work backwards until you can reproduce it again. This will help you pinpoint the cause. – Konrad Rudolph Mar 18 '22 at 22:01

0 Answers0