Problem
I am working on project with RC various custom made classes. I would like to save certain class instances at the end of my program in order to export them to a data base. I tried using jsonlite::toJSON(account) but I received the error message
Error: No method for S4 class:BankAccount
Class
I have the following class
BankAccount <- setRefClass('BankAccount',
fields = list(
balance = 'numeric',
ledger = 'data.frame'
),
methods = list(
deposit = function (x) {
x <- max(x,0)
balance <<- balance + x
ledger <<- data.frame(
Date = c(ledger$Date, as.character(Sys.time())),
Type = c(ledger$Type, 'Deposit'),
Amount = c(ledger$Amount, x),
stringsAsFactors = FALSE
)
},
withdraw = function (x) {
x <- max(x,0)
balance <<- balance - x
ledger <<- data.frame(
Date = c(ledger$Date, as.character(Sys.time())),
Type = c(ledger$Type, 'Withdrawal'),
Amount = c(ledger$Amount, x),
stringsAsFactors = FALSE
)
}
))
Instance
And here is an instance of that class
account <- BankAccount$new(balance = 100)
account$deposit(1000)
Sys.sleep(5)
account$withdraw(97.89)
account
Reference class object of class "BankAccount"
Field "balance":
[1] 1002.11
Field "ledger":
Date Type Amount
1 2018-12-31 16:21:20 Deposit 1000.00
2 2018-12-31 16:21:26 Withdrawal 97.89
JSON
Now I would like to save it to as a JSON file of the form (there might be a typo in the JSON - not that familiar with the format)
{
"balance": "double",
"ledger": {
"Date": "string",
"Type": "string",
"Amount": "double"
}
}
PS
I also tried without the field ledger (which is of class data.frame) but it still did not work.
Edit
Here is the output of jsonlite::serializeJSON(account)
{"type":"S4","attributes":{".xData":{"type":"environment","attributes":{},"value":{}}},"value":{"class":"BankAccount","package":".GlobalEnv"}}
As you can see it seems to only save information about the class BankAccount but not about the instance account (balance value etc. missing).
Here is a workaround (as indicated by #StefanF). One can add methods to classes
# converts to JSON
toJSON = function (prettifyy = TRUE) {
instanceJSON <- jsonlite::toJSON(list(balance = balance,ledger = ledger))
if (prettifyy) instanceJSON <- jsonlite::prettify(instanceJSON)
instanceJSON
}
# saves as .json, e.g. path = 'C:/Test/instance.json'
saveJSON = function (path) {
instanceJSON <- toJSON()
writeLines(instanceJSON, path)
}
The solution is not ideal as
there is a bit of labor as one needs to specify which of the fields should be incorporated
also, if a field of BankAccount is of class MyClass (another custom class), then you need to either specify which fields of MyClass are of relevance or create a toJSON as well for MyClass
Related
I'm looking at the code example provided in the R6 class documentation here.
This is the class definition:
Person <- R6Class("Person",
private = list(
.age = NA,
.name = NULL
),
active = list(
age = function(value) {
if (missing(value)) {
private$.age
} else {
stop("`$age` is read only", call. = FALSE)
}
},
name = function(value) {
if (missing(value)) {
private$.name
} else {
stopifnot(is.character(value), length(value) == 1)
private$.name <- value
self
}
}
),
public = list(
initialize = function(name, age = NA) {
private$.name <- name
private$.age <- age
}
)
)
The important part is how the age field is wrapped to be read-only, and the name field is wrapped to perform a validation check before making the assignment.
In my own usecase, I'm not interested in the read-only part, but I am implementing the validation logic. So, focus on the active field for name.
I do not understand why they are following the pattern to return self after the call. How can you chain together assignments? Assuming age wasn't read only, how would it look to try to assign age and name in a chain? I've tried a number of ways, and it never seems to work:
p <- Person$new();
p$age <- 10$name <- "Jill" # obviously doesn't work because how can you reference the name field of 10?
(p$age <- 10)$name <- "Jill" # this looks more likely to work but the parens don't help
p$age(10)$name("Jill") # Does not work, you can't invoke active fields as if they were functions.
# other syntax options?
So, the heart of my question is: if you're using active fields in R6 classes to facilitate some type-checking prior to assignment, you can't really chain those operations together, so why does the official documentation show returning self in the field accessors?
I've been working on an R package that is just a REST API wrapper for a graph database. I have a function createNode that returns an object with class node and entity:
# Connect to the db.
graph = startGraph("http://localhost:7474/db/data/")
# Create two nodes in the db.
alice = createNode(graph, name = "Alice")
bob = createNode(graph, name = "Bob")
> class(alice)
[1] "node" "entity"
> class(bob)
[1] "node" "entity"
I have another function, createRel, that creates a relationship between two nodes in the database. It is specified as follows:
createRel = function(fromNode, type, toNode, ...) {
UseMethod("createRel")
}
createRel.default = function(fromNode, ...) {
stop("Invalid object. Must supply node object.")
}
createRel.node = function(fromNode, type, toNode, ...) {
params = list(...)
# Check if toNode is a node.
stopifnot("node" %in% class(toNode))
# Making REST API calls through RCurl and stuff.
}
The ... allows the user to add an arbitrary amount of properties to the relationship in the form key = value. For example,
rel = createRel(alice, "KNOWS", bob, since = 2000, through = "Work")
This creates an (Alice)-[KNOWS]->(Bob) relationship in the db, with the properties since and through and their respective values. However, if a user specifies properties with keys from or to in the ... argument, R gets confused about the classes of fromNode and toNode.
Specifying a property with key from creates confusion about the class of fromNode. It is using createRel.default:
> createRel(alice, "KNOWS", bob, from = "Work")
Error in createRel.default(alice, "KNOWS", bob, from = "Work") :
Invalid object. Must supply node object.
3 stop("Invalid object. Must supply node object.")
2 createRel.default(alice, "KNOWS", bob, from = "Work")
1 createRel(alice, "KNOWS", bob, from = "Work")
Similarly, if a user specifies a property with key to, there is confusion about the class of toNode, and stops at the stopifnot():
Error: "node" %in% class(toNode) is not TRUE
4 stop(sprintf(ngettext(length(r), "%s is not TRUE", "%s are not all TRUE"),
ch), call. = FALSE, domain = NA)
3 stopifnot("node" %in% class(toNode))
2 createRel.node(alice, "KNOWS", bob, to = "Something")
1 createRel(alice, "KNOWS", bob, to = "Something")
I've found that explicitly setting the parameters in createRel works fine:
rel = createRel(fromNode = alice,
type = "KNOWS",
toNode = bob,
from = "Work",
to = "Something")
# OK
But I am wondering how I need to edit my createRel function so that the following syntax will work without error:
rel = createRel(alice, "KNOWS", bob, from = "Work", to = "Something")
# Errors galore.
The GitHub user who opened the issue mentioned it is most likely a conflict with setAs on dispatch, which has arguments called from and to. One solution is to get rid of ... and change createRel to the following:
createRel = function(fromNode, type, toNode, params = list()) {
UseMethod("createRel")
}
createRel.default = function(fromNode, ...) {
stop("Invalid object. Must supply node object.")
}
createRel.node = function(fromNode, type, toNode, params = list()) {
# Check if toNode is a node.
stopifnot("node" %in% class(toNode))
# Making REST API calls through RCurl and stuff.
}
But, I wanted to see if I had any other options before making this change.
Not really an answer, but...
The problem is that the user-provided argument 'from' is being (partially) matched to the formal argument 'fromNode'.
f = function(fromNode, ...) fromNode
f(1, from=2)
## [1] 2
The rules are outlined in section 4.3.2 of RShowDoc('R-lang'), where named arguments are exact matched, then partial matched, and then unnamed arguments are assigned by position.
It's hard to know how to enforce exact matching, other than using single-letter argument names! Actually, for a generic this might not be as trite as it sounds -- x is a pretty generic variable name. If 'from' and 'to' were common arguments to ... you could change the argument list to "fromNode, , ..., from, to", check for missing(from) in the body of the function, and act accordingly; I don't think this would be pleasant, and the user would invariable provide an argument 'fro'.
While enforcing exact matching (and errors, via warn=2) by setting global options() might be helpful in debugging (though by then you'd probably know what you were looking for!) it doesn't help the package author who is trying to write code to work for users in general.
It might be reasonable to ask on the R-devel mailing list whether it might be time for this behavior to be changed (on the 'several releases' time scale); partial matching probably dates as a 'convenience' from the days before tab completion.
For example:
Mycls = setRefClass(
"Mycls",
fields = list(
# this is just a mock up
colorvec = "numeric" | "factor" | "matrix"
)
)
In this example, I want to allow colorvec to be numeric or factor or matrix.
Is there a way to do this?
Three possibilities.
Use the ANY type.
m2 = setRefClass("m2",
fields = list(x="ANY")
)
which, as the name, suggests allows you to have any type.
Create another class that only accepts numerics/factors/matrices:
setClass("mult", representation(x="ANY"))
setValidity("mult",
function(object)
is.numeric(object#x) || is.factor(object#x) || is.matrix(object#x)
)
m3 = setRefClass("m3", fields = list(x="mult"))
So
bit = new("mult", x=10)
m3$new(x=bit)
Have a function as your input and check the types. Notice that the x field doesn't actually store any data, it just checks and returns the internal value. You could create a simple show method to hide the internal field.
m4 = setRefClass("m4",
fields=list(x = function(y){
if(!missing(y) && !is.null(y)) {
if(!(is.numeric(y))){
stop("Wrong type")
}
internal <<- y
} else internal}
,
internal="ANY"
))
m4$new(x=10)
m4$new(x="10")
This question already has an answer here:
`print` method for ReferenceClass
(1 answer)
Closed 8 years ago.
I have a ReferenceClass in R.
How can I add a method "print()" to it, which will print the values of all of the fields in the class?
Perhaps a better implementation is the following
Config = setRefClass("Config",
fields = list(
ConfigBool = "logical",
ConfigString = "character"),
methods = list(
## Allow ... and callSuper for proper initialization by subclasses
initialize = function(...) {
callSuper(..., ConfigBool=TRUE, ConfigString="A configuration string")
## alterantive:
## callSuper(...)
## initFields(ConfigBool=TRUE, ConfigString="A configuration string")
},
## Implement 'show' method for automatic display
show = function() {
flds <- getRefClass()$fields()
cat("* Fields\n")
for (fld in names(flds)) # iterate over flds, rather than index of flds
cat(' ', fld,': ', .self[[fld]], '\n', sep="")
})
)
The following illustrates use of the Config constructor (no need to invoke 'new') and automatic invocation of 'show'
> Config()
* Fields
ConfigBool: TRUE
ConfigString: A configuration string
Run the following demo in an R console:
# Reference Class to store configuration
Config <- setRefClass("Config",
fields = list(
ConfigBool = "logical",
ConfigString = "character"
),
methods = list(
# Constructor.
initialize = function(x) {
ConfigBool <<- TRUE
ConfigString <<- "A configuration string"
},
# Print the values of all of the fields used in this class.
print = function(values) {
cat("* Fields\n")
fieldList <- names(.refClassDef#fieldClasses)
for(fi in fieldList)
{
variableName = fi
variableValue = field(fi)
cat(' ',variableName,': ',variableValue,'\n',sep="")
}
}
)
)
config <- Config$new()
config
config$print()
---test code---
# Demos how to print the fields of the class using built-in default "show()" function.
> config$show()
Reference class object of class "Config"
Field "ConfigBool":
[1] TRUE
Field "ConfigString":
[1] "A configuration string"
# Omitting the "show()" function has the same result, as show() is called by default.
> config
Reference class object of class "Config"
Field "ConfigBool":
[1] TRUE
Field "ConfigString":
[1] "A configuration string"
# Demos how to print the fields of the class using our own custom "print" function.
> config$print()
* Fields
ConfigBool: TRUE
ConfigString: A configuration string
In addition, typing the following shows the source code for the default "show" function that is included with all ReferenceClasses:
config$show
I'm a huge fan of S4 Reference Classes as they allow for a hybrid programming style (functional/pass-by-value vs. oop/pass-by-reference; example) and thus increase flexibility dramatically.
However, I think I just came across an undesired behavior with respect to the way R scans through environments/frames when you ask it to retrieve a certain field value via method $field() (see help page). The problem is that R also seems to look in enclosing environments/frames if the desired field is not found in the actual local/target environment (which would be the environment making up the S4 Reference Class), i.e. it's just like running get(<objname>, inherits=TRUE) (see help page).
Actual question
In order to have R just look in the local/target environment, I was thinking something like $field(name="<fieldname>", inherits=FALSE) but $field() doesn't have a ... argument that would allow me to pass inherits=FALSE along to get() (which I'm guessing is called somewhere along the way). Is there a workaround to this?
Code Example
For those interested in more details: here's a little code example illustrating the behavior
setRefClass("A", fields=list(a="character"))
x <- getRefClass("A")$new(a="a")
There is a field a in class A, so it's found in the target environment and the value is returned:
> x$field("a")
[1] "a"
Things look differently if we try to access a field that is not a field of the reference class but happens to have a name identical to that of some other object in the workspace/searchpath (in this case "lm"):
require("MASS")
> x$field("lm")
function (formula, data, subset, weights, na.action, method = "qr",
model = TRUE, x = FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE,
contrasts = NULL, offset, ...)
{
ret.x <- x
ret.y <- y
[omitted]
if (!qr)
z$qr <- NULL
z
}
<bytecode: 0x02e6b654>
<environment: namespace:stats>
Not really what I would expect at this point. IMHO an error or at least a warning would be much better. Or opening method $field() for arguments that can be passed along to other functions via .... I'm guessing somewhere along the way get() is called when calling $field(), so something like this could prevent the above behavior from occurring:
x$field("digest", inherits=FALSE)
Workaround: own proposal
This should do the trick, but maybe there's something more elegant that doesn't involve the specification of a new method on top of $field():
setRefClass("A", fields=list(a="character"),
methods=list(
myField=function(name, ...) {
# VALIDATE NAME //
if (!name %in% names(getRefClass(class(.self))$fields())) {
stop(paste0("Invalid field name: '", name, "'"))
}
# //
.self$field(name=name)
}
)
)
x <- getRefClass("A")$new(a="a")
> x$myField("a")
[1] "a"
> x$myField("lm")
Error in x$myField("lm") : Invalid field name: 'lm'
The default field() method can be replaced with your own. So adding an inherits argument to avoid the enclosing frames is simply a matter of grabbing the existing x$field definition and adding it...
setRefClass( Class="B",
fields= list( a="character" ),
methods= list(
field = function(name, value, inherits=TRUE ) {
if( missing(value) ) {
get( name, envir=.self, inherits=inherits )
} else {
if( is.na( match( name, names( .refClassDef#fieldClasses ) ) ) ) {
stop(gettextf("%s is not a field in this class", sQuote(name)), domain = NA)
}
assign(name, value, envir = .self)
}
}
),
)
Or you could have a nice error message with a little rearranging
setRefClass( Class="C",
fields= list( a="character" ),
methods= list(
field = function(name, value, inherits=TRUE ) {
if( is.na( match( name, names( .refClassDef#fieldClasses ) ) ) &&
( !missing(value) || inherits==FALSE) ) {
stop(gettextf("%s is not a field in this class", sQuote(name)), domain = NA)
}
if( missing(value) ) {
get( name, envir=.self, inherits=inherits )
} else {
assign(name, value, envir = .self)
}
}
),
)
Since you can define any of your own methods to replace the defaults pretty much any logic you want can be implemented for your refclasses. Perhaps an error if the variable is acquired using inheritance but the mode matches to c("expression", "name", "symbol", "function") and warning if it doesn't directly match the local refClass field names?