Skip to content

Commit

Permalink
Flexible default args and cache reset
Browse files Browse the repository at this point in the history
- Flexible default arguments in 'cafun_create'
- Reset of internal cache ('cafun_reset_cache')
- Added BACKLOG.md
- Added examples
- Refined tests
  • Loading branch information
Janko Thyson committed Feb 4, 2018
1 parent 9a1f421 commit 8b1a4c5
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 16 deletions.
15 changes: 15 additions & 0 deletions BACKLOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# cachefun 0.1.0

* Carefully think about default setting of `refresh`.

Really depends on the most frequent use case: avoid unnecessary re-executions of long-running functions (probably mostly linked to data I/O and data wrangling) or avoid confusion through forgetting to refresh cached results.

It's probably best to go with `refresh = TRUE` here.

* Is context information via `.verbose` really that relevant?

Adds clarity, but hurts performance and I don't like the current implementation of `.verbose` in the `shiny::reactive`.

* Find out what's best practice regarding setting defaults arg values in inner function returned by `cafun_create`

* Understand **where** the cached information is actually stored and how it can be purged (to allow explicit purges/removals like via `rm`)
5 changes: 3 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: cachefun
Type: Package
Title: Functions with built-in cache
Version: 0.1.0
Version: 0.1.1
Author: Janko Thyson
Maintainer: Janko Thyson <janko.thyson@rappster.io>
Description: Allows to define functions that are able to cache their return value
Expand All @@ -11,4 +11,5 @@ LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 6.0.1
Suggests:
covr
covr,
testthat
8 changes: 7 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
exportPattern("^[[:alpha:]]+")
# Generated by roxygen2: do not edit by hand

export(cafun_create)
export(cafun_reset_cache)
importFrom(shiny,isolate)
importFrom(shiny,reactive)
importFrom(shiny,reactiveValues)
7 changes: 6 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# cachefun 0.1.0

* Added a `NEWS.md` file to track changes to the package.
* Initial version

# cachefun 0.1.1

* Variable default settings
* Reset of internal cache
37 changes: 37 additions & 0 deletions R/auxiliary.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

# Aux ---------------------------------------------------------------------

get_env_to_assign_to <- function(
use_env = c("temp", "global", "package")
) {
# nsName <- "temp"
# (ns <- asNamespace(nsName)) # <environment: namespace:stats>
# Keep as reference

# as.environment( "package:dlaker")
# Keep as reference

# getPackageName(2)
# Get name that corresponds to second entry on the search list
# Keep as reference

use_env <- match.arg(use_env)

switch(use_env,
"temp" = as.environment("._dlaker_temp"),
"global" = .GlobalEnv,
# "package" = as.environment(search()[2])

# TODO-20180131-2:
# This seems VERY fragile/dangerous and probably only
# makes sense in context where the datacon generics/methods should be part
# of a PACKAGE that the developer is building. Not sure what the best
# option would be in use cases where a developer would work in a PROJECT
# setting (i.e. only applying packages to solve actual tasks)

"package" = as.environment(sprintf("package:%s", devtools::as.package(".")))
# TODO-20180131-2-SOLVED:
# This seems to be much more robust regarding the package's namespace
# environment position on the search path
)
}
71 changes: 71 additions & 0 deletions R/internal_cache.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Internal cache factory --------------------------------------------------

#' Create cache-aware function
#' @importFrom shiny reactiveValues
#' @importFrom shiny reactive
#' @importFrom shiny isolate
#' @example inst/examples/ex-cafun_main.R
#' @export
cafun_create <- function(
fun = NULL,
.refresh = FALSE,
.verbose = FALSE

) {
reactv <- shiny::reactiveValues()
dat_reactive <- shiny::reactive({
if(reactv$.verbose) message('Caching result...')
reactv$data
})

cafun <- function(
fun,
refresh = TRUE,
reset = FALSE,
.verbose = FALSE,
...
) {
# Reset -----
if (reset) {
if (.verbose) message(shiny::isolate(object.size(dat_reactive())))
# rm(data, envir = reactv)
reactv$data <<- NULL
if (.verbose) message(shiny::isolate(object.size(dat_reactive())))
return(invisible(NULL))
}

# Transfer settings -----
reactv$.verbose <<- .verbose

if (shiny::isolate(is.null(reactv$data)) | refresh) {
fun_res <- fun(...)
# fun_res <- rlang::eval_tidy(fun())
reactv$data <<- fun_res
}

# Relay cache-handling to shiny -----
shiny::isolate(dat_reactive())
}

# Transfer default values -----

# TODO-20180204-1:
# This seems too inolved >> find better solution

.formals <- formals(cafun)
if (!is.null(fun)) .formals$fun <- fun
.formals$refresh <- .refresh
.formals$.verbose <- .verbose
formals(cafun) <- .formals

cafun
}

# Reset cache -------------------------------------------------------------

#' Reset internal cache of cache-aware function
#' @example inst/examples/ex-cafun_main.R
#' @export
cafun_reset_cache <- function(cafun, .verbose = FALSE) {
cafun(reset = TRUE, .verbose = .verbose)
}
46 changes: 46 additions & 0 deletions inst/examples/ex-cafun_main.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Define a regular function that you'd like to make "cache-aware"
fun <- function() Sys.time()

# Turn this function into a cache-aware function
cafun <- cafun_create(fun = fun)

str(cafun)
# Note that default value for arg 'refresh' >> you need to explicitly refresh

cafun() # Initial execution of your inner/actual function 'fun'
cafun() # Subsequent execution: internally cached result of 'fun' is returned
cafun(refresh = TRUE) # Explicit refresh request: 'fun' is re-executed and
# new result is cached internally
cafun() # Subsequent execution: internally cached result of 'fun' is returned

# -----

# Change the default value of args
cafun <- cafun_create(fun = fun, .refresh = TRUE)

str(cafun)
# Note that default value for arg 'refresh' is not 'FALSE' >> you don't need to
# explicitly refresh the internal cache. However, now you must explictly state
# when you **don't** want to refresh the cache - or to put in other words - when
# you want to make use of the internal cache

cafun() # Inner function executed
cafun() # Inner function re-executed
cafun(refresh = FALSE) # Internal cache value returned
cafun(refresh = FALSE) # Internal cache value returned
cafun() # Inner function re-executed

# -----

# Inner function with argumeents
fun <- function(x) Sys.time() + x

cafun <- cafun_create(fun = fun)

cafun(x = 3600) # Inner function executed
cafun(x = 3600 * 5) # Internal cache value returned
cafun(x = 3600 * 5, refresh = TRUE) # Inner function re-executed

# -----

# Reset internal cache
59 changes: 59 additions & 0 deletions man/cafun_create.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions man/cafun_reset_cache.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 0 additions & 12 deletions man/hello.Rd

This file was deleted.

4 changes: 4 additions & 0 deletions tests/testthat.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
library(testthat)
library(cachefun)

test_check("cachefun")
38 changes: 38 additions & 0 deletions tests/testthat/test-cacheaware_get.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
library(testthat)
context("Test cache-aware functions")

test_that("20180204-1: cache-aware function: initial", {
expect_is(fun_with_cache <- cafun_create(), "function")

fun <- function() "hello world!"
expectation <- "hello world!"
expect_identical(fun_with_cache(fun = fun), expectation)
expect_identical(fun_with_cache(fun = fun), expectation)

fun <- function() "hello WORLD!"
expectation <- "hello WORLD!"
expect_identical(fun_with_cache(fun = fun, refresh = TRUE), expectation)
expect_identical(fun_with_cache(fun = fun), expectation)
})

test_that("20180204-2: cache-aware function: verbose", {
expect_is(fun_with_cache <- cafun_create(), "function")

fun <- function() "hello world!"
expectation <- "hello world!"
expect_message(
expect_identical(fun_with_cache(fun = fun, .verbose = TRUE), expectation),
"Caching result"
)
})

test_that("20180204-3: cache-aware function: reset cache", {
# Inner function with argumeents
fun <- function(x) rnorm(x)

cafun <- cafun_create(fun = fun)

res <- cafun(x = 3600)

cafun_reset_cache(cafun = cafun, .verbose = TRUE)
})

0 comments on commit 8b1a4c5

Please sign in to comment.