I'd like to make a basic profiling tool that collects time stamps and produces run times with a note. The only problem is I'm having trouble figuring out how to do this without using global variables. What's the "correct" way to implement the functionality I'm trying to achieve? If R already has this functionality built in, that's awesome, but what I'm really trying to figure out here is how to avoid using global variables and write more robust code.
timeStamps = c()
runTimes = list()
appendRunTimes <- function(note) {
if(length(timeStamps) < 1) {
timeStamps <<- Sys.time()
}
else {
timeStamps <<- c(timeStamps, Sys.time())
diff <- timeStamps[length(timeStamps) ] - timeStamps[length(timeStamps) - 1]
runTimes <<- c(runTimes, format(diff))
names(runTimes)[length(runTimes)] <<- note
}
}
appendRunTimes('start')
Sys.sleep(4)
appendRunTimes('test')
Never write 'global'. Then you are sure you are not introducing any global variables. This is very good advice--you basically never need this keyword.
In a scenario, where you need one central global point of access across the codebase, singletons are a good alternative for global variables. We can call a singleton as a lazily initialized global class which is useful when you have large objects — memory allocation can be deferred till when it's actually needed.
Non-const global variables are evil because their value can be changed by any function. Using global variables reduces the modularity and flexibility of the program. It is suggested not to use global variables in the program. Instead of using global variables, use local variables in the program.
Solution(By Examveda Team) One way for a module to avoid the creation of global variables is to use an object as its namespace. Instead of defining global functions and variables, it stores the functions and values as properties of an object (which may be referenced to a global variable).
Here's your example rewritten using closures:
RTmonitor <- local({
timeStamps = c()
runTimes = list()
list(
appendRunTimes=function(note) {
if(length(timeStamps) < 1) {
timeStamps <<- Sys.time()
}
else {
timeStamps <<- c(timeStamps, Sys.time())
diff <- timeStamps[length(timeStamps) ] - timeStamps[length(timeStamps) - 1]
runTimes <<- c(runTimes, format(diff))
names(runTimes)[length(runTimes)] <<- note
}
},
viewRunTimes=function() {
return(list(timeStamps=timeStamps,runTimes=runTimes))
})
})
> RTmonitor$appendRunTimes("start")
> RTmonitor$appendRunTimes("test")
> RTmonitor$viewRunTimes()
$timeStamps
[1] "2013-01-04 18:39:12 EST" "2013-01-04 18:39:21 EST"
$runTimes
$runTimes$test
[1] "8.855587 secs"
Observe that the values are stored inside the closure, not in the global environment:
> timeStamps
Error: object 'timeStamps' not found
> runTimes
Error: object 'runTimes' not found
> RTmonitor$timeStamps
NULL
> RTmonitor$runTimes
NULL
More reading on closures and avoiding globals:
Here's now to do this using ReferenceClasses:
RTmonitor = setRefClass("RTmonitor",
fields=list(
timeStamps="POSIXct",
runTimes = "list"
),
methods=list(
appendRunTimes=function(note){
if(length(timeStamps)==0){
timeStamps <<- Sys.time()
}else{
timeStamps <<- c(timeStamps, Sys.time())
diff <- timeStamps[length(timeStamps) ] - timeStamps[length(timeStamps) - 1]
runTimes <<- c(runTimes, format(diff))
names(runTimes)[length(runTimes)] <<- note
}
}
)
)
Now you've defined a class, instantiate an object and use it:
> r = RTmonitor$new()
> r$appendRunTimes("start")
> r$appendRunTimes("test")
> r
Reference class object of class "RTmonitor"
Field "timeStamps":
[1] "2013-01-05 14:52:25 GMT" "2013-01-05 14:52:31 GMT"
Field "runTimes":
$test
[1] "5.175815 secs"
Very similar to the closure approach, but more formalised. I had to define the timeStamps
field as POSIXct
, for example. You can also create multiple RTmonitor
objects this way and they work independently - you'd have to write a closure constructor that wrapped the closure to do that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With