Define an S3 method for a generic class [duplicate] - r

This question already has answers here:
Making a string concatenation operator in R
(5 answers)
Closed 4 years ago.
I would like to create a + method to paste character objects. One possible approach is by doing it with an infix operator:
`%+%` <- function(x, y) paste0(x, y)
"a" %+% "b" # returns "ab"
However, I'm wondering if it is possible to do the same with an S3 method. You could do it by creating a new class, let's say char, and do something like:
# Create a function to coerce to class 'char'
as.char <- function(x, ...) structure(as.character(x, ...), class = "char")
# S3 method to paste 'char' objects
"+.char" <- function(x, y) paste0(x, y)
a <- as.char("a")
b <- as.char("b")
c(class(a), class(b)) # [1] "char" "char"
a + b # returns "ab"
But, if you try to create a +.character method, it does not work:
"+.character" <- function(x, y) paste0(x, y)
a <- "a"
b <- "b"
c(class(a), class(b)) # [1] "character" "character"
a + b # Error in a + b : non-numeric argument to binary operator
However, if you assign the class character manually it does work:
as.character_ <- function(x, ...) structure(as.character(x, ...), class = "character")
a <- as.character_("a")
b <- as.character_("b")
c(class(a), class(b)) # [1] "character" "character"
a + b # returns "ab"
So I'm just wondering what I'm missing here, and if it is possible to actually define an S3 method for a generic class.
Edit: Based on #Csd answer, it is not clear that this is due to the attributes, because if you define your own function, e.g.:
concat <- function(e1, e2) UseMethod("concat")
concat.character <- function(e1, e2) paste0(e1, e2)
concat("a", "b") # returns "ab"
Then it does work.

It seems that you need to define the class of the variable to be "character". That's exactly what you do! except one thing... which I didn't know either...
Here is an example:
a <- "a"
class(a) # "character"
attributes(a) # NULL!!!
while using you function:
a <- as.character_("a")
class(a) # "character"
attributes(a) # "class" is "character"
So it seems that it has to be defined the attribute class of the variable.

Related

Pass vector of symbols as function argument and convert to character vector

I have a function that I want to pass as an argument a vector of symbols and then internally I want to convert that vector to a character vector.
Minimal example:
fun <- function(symbols = c(a, b, c)) {
# code to convert to character vector
}
fun()
Output:
[1] "a" "b" "c"
Here's an approach with rlang::quo_name:
library(rlang)
fun <- function(symbols = c(a, b, c)) {
symbols <- enquo(symbols)
string <- quo_name(symbols)
unlist(strsplit(gsub("(c\\(|\\)|\\s)","",string),","))
}
fun(c(apple, orange, pear))
#[1] "apple" "orange" "pear"
I suspect you're actually trying to solve another problem with this, so it probably makes sense to post that as another question.
Base R solution:
fun <- function(symbols = c(a, b, c)) {
# code to convert to character vector
return(unlist(strsplit(
gsub("c\\(|\\)|\\(|\\s+", "",
deparse(substitute(symbols))), ","
)))
}
fun()

With rlang, convert contents of `...` to a character vector

I'd like to be able to create a character vector based on the names supplied to the ... part of a function.
For instance, if I have the function foo(...) and I type foo(x, y), how do I create a character vector that looks like c("x", "y")?
I'm most interested in figuring out how to use rlang for this, but base solutions would be great as well.
Do you mean something like this?
foo <- function(...) unname(purrr::map_chr(rlang::exprs(...), as.character))
foo(x, y)
#[1] "x" "y"
identical(foo(x, y), c("x", "y"))
#[1] TRUE
Alternatively we can use as.character directly on the list returned from rlang::exprs
foo <- function(...) as.character(rlang::exprs(...))
In response to #joran's question, I'm not sure to be honest; consider the following case
as.character(rlang::exprs(NULL, a, b))
#[1] "NULL" "a" "b"
map_chr(rlang::exprs(NULL, a, b), as.character)
#Error: Result 1 is not a length 1 atomic vector
So as.character converts NULL to "NULL" whereas map_chr(..., as.character) throws an error on account of the NULL list entry.

Get the argument names of an R function

For an arbitrary function
f <- function(x, y = 3){
z <- x + y
z^2
}
I want to be able take the argument names of f
> argument_names(f)
[1] "x" "y"
Is this possible?
formalArgs and formals are two functions that would be useful in this case. If you just want the parameter names then formalArgs will be more useful as it just gives the names and ignores any defaults. formals gives a list as the output and provides the parameter name as the name of the element in the list and the default as the value of the element.
f <- function(x, y = 3){
z <- x + y
z^2
}
> formalArgs(f)
[1] "x" "y"
> formals(f)
$x
$y
[1] 3
My first inclination was to just suggest formals and if you just wanted the names of the parameters you could use names like names(formals(f)). The formalArgs function just is a wrapper that does that for you so either way works.
Edit: Note that technically primitive functions don't have "formals" so this method will return NULL if used on primitives. A way around that is to first wrap the function in args before passing to formalArgs. This works regardless of it the function is primitive or not.
> # formalArgs will work for non-primitives but not primitives
> formalArgs(f)
[1] "x" "y"
> formalArgs(sum)
NULL
> # But wrapping the function in args first will work in either case
> formalArgs(args(f))
[1] "x" "y"
> formalArgs(args(sum))
[1] "..." "na.rm"

R: Argument as variablename and string in function? [duplicate]

I am looking for the reverse of get().
Given an object name, I wish to have the character string representing that object extracted directly from the object.
Trivial example with foo being the placeholder for the function I am looking for.
z <- data.frame(x=1:10, y=1:10)
test <- function(a){
mean.x <- mean(a$x)
print(foo(a))
return(mean.x)}
test(z)
Would print:
"z"
My work around, which is harder to implement in my current problem is:
test <- function(a="z"){
mean.x <- mean(get(a)$x)
print(a)
return(mean.x)}
test("z")
The old deparse-substitute trick:
a<-data.frame(x=1:10,y=1:10)
test<-function(z){
mean.x<-mean(z$x)
nm <-deparse(substitute(z))
print(nm)
return(mean.x)}
test(a)
#[1] "a" ... this is the side-effect of the print() call
# ... you could have done something useful with that character value
#[1] 5.5 ... this is the result of the function call
Edit: Ran it with the new test-object
Note: this will not succeed inside a local function when a set of list items are passed from the first argument to lapply (and it also fails when an object is passed from a list given to a for-loop.) You would be able to extract the ".Names"-attribute and the order of processing from the structure result, if it were a named vector that were being processed.
> lapply( list(a=4,b=5), function(x) {nm <- deparse(substitute(x)); strsplit(nm, '\\[')} )
$a # This "a" and the next one in the print output are put in after processing
$a[[1]]
[1] "X" "" "1L]]" # Notice that there was no "a"
$b
$b[[1]]
[1] "X" "" "2L]]"
> lapply( c(a=4,b=5), function(x) {nm <- deparse(substitute(x)); strsplit(nm, '\\[')} )
$a
$a[[1]] # but it's theoretically possible to extract when its an atomic vector
[1] "structure(c(4, 5), .Names = c(\"a\", \"b\"))" ""
[3] "1L]]"
$b
$b[[1]]
[1] "structure(c(4, 5), .Names = c(\"a\", \"b\"))" ""
[3] "2L]]"
deparse(quote(var))
My intuitive understanding
In which the quote freeze the var or expression from evaluation
and the deparse function which is the inverse of parse function makes that freezed symbol back to String
Note that for print methods the behavior can be different.
print.foo=function(x){ print(deparse(substitute(x))) }
test = list(a=1, b=2)
class(test)="foo"
#this shows "test" as expected
print(test)
#this (just typing 'test' on the R command line)
test
#shows
#"structure(list(a = 1, b = 2), .Names = c(\"a\", \"b\"), class = \"foo\")"
Other comments I've seen on forums suggests that the last behavior is unavoidable. This is unfortunate if you are writing print methods for packages.
To elaborate on Eli Holmes' answer:
myfunc works beautifully
I was tempted to call it within another function (as discussed in his Aug 15, '20 comment)
Fail
Within a function, coded directly (rather than called from an external function), the deparse(substitute() trick works well.
This is all implicit in his answer, but for the benefit of peeps with my degree of obliviousness, I wanted to spell it out.
an_object <- mtcars
myfunc <- function(x) deparse(substitute(x))
myfunc(an_object)
#> [1] "an_object"
# called within another function
wrapper <- function(x){
myfunc(x)
}
wrapper(an_object)
#> [1] "x"

How to void type conversion in R's apply (bit64 example)

I am using the bit64 package in some R code. I have created a vector
of 64 bit integers and then tried to use sapply to iterate over these
integers in a vector. Here is an example:
v = c(as.integer64(1), as.integer64(2), as.integer64(3))
sapply(v, function(x){is.integer64(x)})
sapply(v, function(x){print(x)})
Both the is.integer64(x) and print(x) give the incorrect
(or at least) unexpected answers (FALSE and incorrect float values).
I can circumvent this by directly indexing the vector c but I have
two questions:
Why the type conversion? Is their some rule R uses in such a scenario?
Any way one can avoid this type conversion?
TIA.
Here is the code of lapply:
function (X, FUN, ...)
{
FUN <- match.fun(FUN)
if (!is.vector(X) || is.object(X))
X <- as.list(X)
.Internal(lapply(X, FUN))
}
Now check this:
!is.vector(v)
#TRUE
as.list(v)
#[[1]]
#[1] 4.940656e-324
#
#[[2]]
#[1] 9.881313e-324
#
#[[3]]
#[1] 1.482197e-323
From help("as.list"):
Attributes may be dropped unless the argument already is a list or
expression.
So, either you creaste a list from the beginning or you add the class attributes:
v_list <- lapply(as.list(v), function(x) {
class(x) <- "integer64"
x
})
sapply(v_list, function(x){is.integer64(x)})
#[1] TRUE TRUE TRUE
The package authours should consider writing a method for as.list. Might be worth a feature request ...

Resources