memoise/0000755000175100001440000000000013076317216011734 5ustar hornikusersmemoise/tests/0000755000175100001440000000000012634167536013105 5ustar hornikusersmemoise/tests/testthat.R0000644000175100001440000000007212616234453015060 0ustar hornikuserslibrary(testthat) library(memoise) test_check("memoise") memoise/tests/testthat/0000755000175100001440000000000013076317216014736 5ustar hornikusersmemoise/tests/testthat/test-memoise.R0000644000175100001440000001546613015336206017501 0ustar hornikuserscontext("memoise") test_that("memoisation works", { fn <- function() { i <<- i + 1; i } i <- 0 expect_warning(fnm <- memoise(fn), NA) expect_equal(fn(), 1) expect_equal(fn(), 2) expect_equal(fnm(), 3) expect_equal(fnm(), 3) expect_equal(fn(), 4) expect_equal(fnm(), 3) expect_false(forget(fn)) expect_true(forget(fnm)) expect_true(forget(fnm)) expect_equal(fnm(), 5) expect_true(is.memoised(fnm)) expect_false(is.memoised(fn)) }) test_that("memoisation depends on argument", { fn <- function(j) { i <<- i + 1; i } i <- 0 expect_warning(fnm <- memoise(fn), NA) expect_equal(fn(1), 1) expect_equal(fn(1), 2) expect_equal(fnm(1), 3) expect_equal(fnm(1), 3) expect_equal(fn(1), 4) expect_equal(fnm(1), 3) expect_equal(fnm(2), 5) expect_equal(fnm(2), 5) expect_equal(fnm(1), 3) expect_equal(fn(2), 6) }) test_that("interface of wrapper matches interface of memoised function", { fn <- function(j) { i <<- i + 1; i } i <- 0 expect_equal(formals(fn), formals(memoise(fn))) expect_equal(formals(runif), formals(memoise(runif))) expect_equal(formals(paste), formals(memoise(paste))) }) test_that("dot arguments are used for hash", { fn <- function(...) { i <<- i + 1; i } i <- 0 expect_warning(fnm <- memoise(fn), NA) expect_equal(fn(1), 1) expect_equal(fnm(1), 2) expect_equal(fnm(1), 2) expect_equal(fnm(1, 2), 3) expect_equal(fnm(1), 2) expect_equal(fnm(1, 2), 3) expect_equal(fnm(), 4) expect_true(forget(fnm)) expect_equal(fnm(1), 5) expect_equal(fnm(1, 2), 6) expect_equal(fnm(), 7) }) test_that("default arguments are used for hash", { fn <- function(j = 1) { i <<- i + 1; i } i <- 0 expect_warning(fnm <- memoise(fn), NA) expect_equal(fn(1), 1) expect_equal(fnm(1), 2) expect_equal(fnm(1), 2) expect_equal(fnm(), 2) expect_equal(fnm(2), 3) expect_equal(fnm(), 2) }) test_that("default arguments are evaluated correctly", { expect_false(exists("g")) g <- function() 1 fn <- function(j = g()) { i <<- i + 1; i } i <- 0 expect_warning(fnm <- memoise(fn), NA) expect_equal(fn(1), 1) expect_equal(fnm(1), 2) expect_equal(fnm(1), 2) expect_equal(fnm(), 2) expect_equal(fnm(2), 3) expect_equal(fnm(), 2) }) test_that("symbol collision", { cache <- function(j = 1) { i <<- i + 1; i } i <- 0 cachem <- memoise(cache) expect_equal(cache(), 1) expect_equal(cache(), 2) expect_equal(cachem(), 3) expect_equal(cachem(), 3) expect_equal(cache(), 4) expect_equal(cachem(), 3) expect_true(forget(cachem)) expect_equal(cachem(), 5) }) test_that("visibility", { vis <- function() NULL invis <- function() invisible() expect_true(withVisible(memoise(vis)())$visible) expect_false(withVisible(memoise(invis)())$visible) }) test_that("is.memoised", { i <- 0 expect_false(is.memoised(i)) expect_false(is.memoised(is.memoised)) expect_true(is.memoised(memoise(identical))) }) test_that("visibility", { vis <- function() NULL invis <- function() invisible() expect_true(withVisible(memoise(vis)())$visible) expect_false(withVisible(memoise(invis)())$visible) }) test_that("can memoise anonymous function", { expect_warning(fm <- memoise(function(a = 1) a), NA) expect_equal(names(formals(fm))[[1]], "a") expect_equal(fm(1), 1) expect_equal(fm(2), 2) expect_equal(fm(1), 1) }) test_that("can memoise primitive", { expect_warning(fm <- memoise(`+`), NA) expect_equal(names(formals(fm)), names(formals(args(`+`)))) expect_equal(fm(1, 2), 1 + 2) expect_equal(fm(2, 3), 2 + 3) expect_equal(fm(1, 2), 1 + 2) }) test_that("printing a memoised function prints the original definition", { fn <- function(j) { i <<- i + 1; i } fnm <- memoise(fn) fn_output <- capture.output(fn) fnm_output <- capture.output(fnm) expect_equal(fnm_output[1], "Memoised Function:") expect_equal(fnm_output[-1], fn_output) }) test_that("memoisation can depend on non-arguments", { fn <- function(x) { i <<- i + 1; i } i <- 0 j <- 2 fn2 <- function(y, ...) { fnm <- memoise(fn, ~y) fnm(...) } expect_error(memoise(fn, j), "`j` must be a formula\\.") expect_error(memoise(fn, ~j, k), "`k` must be a formula\\.") expect_error(memoise(fn, j ~ 1), "`x` must be a one sided formula \\[not j ~ 1\\]\\.") fnm <- memoise(fn, ~j) expect_equal(fn(1), 1) expect_equal(fn(1), 2) expect_equal(fnm(1), 3) expect_equal(fnm(1), 3) j <- 1 expect_equal(fnm(1), 4) expect_equal(fnm(1), 4) j <- 2 expect_equal(fnm(1), 3) expect_equal(fnm(1), 3) j <- 3 expect_equal(fnm(1), 5) expect_equal(fnm(1), 5) }) test_that("it fails if already memoised", { mem_sum <- memoise(sum) expect_error(memoise(mem_sum), "`f` must not be memoised.") }) test_that("it evaluates arguments in proper environment", { e <- new.env(parent = baseenv()) e$a <- 5 fun <- function(x, y = a) { x + y } environment(fun) <- e fun_mem <- memoise(fun) expect_equal(fun(1), fun_mem(1)) expect_equal(fun(10), fun_mem(10)) }) test_that("it does have namespace clashes with internal memoise symbols", { e <- new.env(parent = baseenv()) e$f <- 5 fun <- function(x, y = f) { x + y } environment(fun) <- e fun_mem <- memoise(fun) expect_equal(fun(1), fun_mem(1)) expect_equal(fun(10), fun_mem(10)) }) test_that("arguments are evaluated before hashing", { i <- 1 f <- memoise(function(x, y, z = 3) { x + y + z}) f2 <- function(x, y) f(x, y) expect_equal(f2(1, 1), 5) expect_equal(f2(1, 1), 5) expect_equal(f2(2, 2), 7) }) context("has_cache") test_that("it works as expected with memoised functions", { mem_sum <- memoise(sum) expect_false(has_cache(mem_sum)(1, 2, 3)) mem_sum(1, 2, 3) expect_true(has_cache(mem_sum)(1, 2, 3)) mem_sum <- memoise(sum) expect_false(has_cache(mem_sum)(1, 2, 3)) }) test_that("it errors with an un-memoised function", { expect_error(has_cache(sum)(1, 2, 3), "`f` is not a memoised function.") }) context("timeout") test_that("it stays the same if not enough time has passed", { duration <- 10 first <- timeout(duration, 0) expect_equal(first, timeout(duration, 1)) expect_equal(first, timeout(duration, 5)) expect_equal(first, timeout(duration, 7)) expect_equal(first, timeout(duration, 9)) expect_true(first != timeout(duration, 10)) duration <- 100 first <- timeout(duration, 0) expect_equal(first, timeout(duration, 10)) expect_equal(first, timeout(duration, 50)) expect_equal(first, timeout(duration, 70)) expect_equal(first, timeout(duration, 99)) expect_true(first != timeout(duration, 100)) }) context("missing") test_that("it works with missing arguments", { fn <- function(x, y) { i <<- i + 1 if (missing(y)) { y <- 1 } x + y } fnm <- memoise(fn) i <- 0 expect_equal(fn(1), fnm(1)) expect_equal(fn(1, 2), fnm(1, 2)) expect_equal(i, 4) fnm(1) expect_equal(i, 4) fnm(1, 2) expect_equal(i, 4) }) memoise/tests/testthat/helper.R0000644000175100001440000000102612775751246016351 0ustar hornikusersskip_without_aws_credetials <- function() { # -# Sys.setenv("AWS_ACCESS_KEY_ID" = "", "AWS_SECRET_ACCESS_KEY" = "") if (nzchar(Sys.getenv("AWS_ACCESS_KEY_ID")) && nzchar(Sys.getenv("AWS_SECRET_ACCESS_KEY"))) { return(invisible(TRUE)) } testthat::skip("No AWS Credentials") } skip_on_travis_pr <- function() { if (identical(Sys.getenv("TRAVIS"), "true") && !identical(Sys.getenv("TRAVIS_PULL_REQUEST", "false"), "false")) { return(testthat::skip("On Travis PR")) } invisible(TRUE) } memoise/tests/testthat/test-filesystem.R0000644000175100001440000000147213076120104020212 0ustar hornikuserscontext("filesystem") test_that("using a filesystem cache works", { fs <- cache_filesystem(tempfile()) i <- 0 fn <- function() { i <<- i + 1; i } fnm <- memoise(fn, cache = fs) on.exit(forget(fnm)) expect_equal(fn(), 1) expect_equal(fn(), 2) expect_equal(fnm(), 3) expect_equal(fnm(), 3) expect_equal(fn(), 4) expect_equal(fnm(), 3) expect_false(forget(fn)) expect_true(forget(fnm)) expect_equal(fnm(), 5) expect_true(is.memoised(fnm)) expect_false(is.memoised(fn)) }) test_that("two functions with the same arguments produce different caches (#38)", { temp <- tempfile() fs <- cache_filesystem(temp) f1 <- memoise(function() 1, cache = fs) f2 <- memoise(function() 2, cache = fs) expect_equal(f1(), 1) expect_equal(f2(), 2) expect_equal(length(list.files(temp)), 2) }) memoise/tests/testthat/test-s3.R0000644000175100001440000000106012776011171016355 0ustar hornikuserscontext("s3") test_that("using a s3 cache works", { skip_on_cran() skip_on_travis_pr() skip_without_aws_credetials() aws <- cache_s3("memoise-tests") i <- 0 fn <- function() { i <<- i + 1; i } fnm <- memoise(fn, cache = aws) on.exit(forget(fnm)) expect_equal(fn(), 1) expect_equal(fn(), 2) expect_equal(fnm(), 3) expect_equal(fnm(), 3) expect_equal(fn(), 4) expect_equal(fnm(), 3) expect_false(forget(fn)) expect_true(forget(fnm)) expect_equal(fnm(), 5) expect_true(is.memoised(fnm)) expect_false(is.memoised(fn)) }) memoise/NAMESPACE0000644000175100001440000000046413003701144013142 0ustar hornikusers# Generated by roxygen2: do not edit by hand S3method(print,memoised) export(cache_filesystem) export(cache_memory) export(cache_s3) export(forget) export(has_cache) export(is.memoised) export(is.memoized) export(memoise) export(memoize) export(timeout) importFrom(digest,digest) importFrom(stats,setNames) memoise/NEWS.md0000644000175100001440000000325313076147015013033 0ustar hornikusers# Version 1.1.0 * Caches now hash the function body along with the arguments, to ensure functions with identical arguments use a separate file-system cache. (#38) * Handle missing arguments in memoised functions for simple cases not using non-standard-evaluation (#19). * `memoise()` gains a `cache=` argument to specify an external cache. Two types of caches are available, `cache_s3()` for amazon S3 and `cache_filesystem()` for a file system cache (#25, @danielecook). # Version 1.0.0 * `memoise()` now signals an error if an already memoised function is used as input (#4, @richierocks). * `has_cache()` function added which returns a boolean depending on if the given call is cached or not (#10, @dkesh). * Memoised functions now have a print method which displays the original function definition, rather than the memoisation code (#15, @jimhester). * A memoised function now has the same interface as the original function, if the original function is known when `memoise` is called. (Otherwise, the old behavior is invoked, with a warning.) (#14, @krlmlr) * The enclosing environment of the memoised function is specified explicitly, defaults to `parent.frame()`. * `is.memoised` now checks if the argument is a function. * Testing infrastructure, full test coverage. # Version 0.2.1 * Update to fix outstanding R CMD check issues. # Version 0.2 (2010-11-11) ## New features * Memoised functions now have an attribute memoised=TRUE, and is.memoised() tests whether a function is memoised. (Contributed by Sietse Brouwer.) ## Improvements * Documentation is now more elaborate, and hopefully more accessible to newcomers. Thanks to Sietse Brouwer for the verbosity. memoise/R/0000755000175100001440000000000013076170563012137 5ustar hornikusersmemoise/R/cache_filesystem.R0000644000175100001440000000226113003701176015560 0ustar hornikusers#' Filesystem Cache #' #' Use a cache on the local filesystem that will persist between R sessions. #' #' @param path Directory in which to store cached items. #' #' @examples #' #' \dontrun{ #' # Use with Dropbox #' #' db <- cache_filesystem("~/Dropbox/.rcache") #' #' mem_runif <- memoise(runif, cache = db) #' #' # Use with Google Drive #' #' gd <- cache_filesystem("~/Google Drive/.rcache") #' #' mem_runif <- memoise(runif, cache = gd) #' #' } #' #' @export #' @inheritParams cache_memory cache_filesystem <- function(path, algo = "xxhash64") { if (!dir.exists(path)) { dir.create(path, showWarnings = FALSE) } cache_reset <- function() { cache_files <- list.files(path, full.names = TRUE) file.remove(cache_files) } cache_set <- function(key, value) { saveRDS(value, file = file.path(path, key)) } cache_get <- function(key) { readRDS(file = file.path(path, key)) } cache_has_key <- function(key) { file.exists(file.path(path, key)) } list( digest = function(...) digest::digest(..., algo = algo), reset = cache_reset, set = cache_set, get = cache_get, has_key = cache_has_key, keys = function() list.files(path) ) } memoise/R/cache_memory.R0000644000175100001440000000146713076117755014731 0ustar hornikusers#' In Memory Cache #' #' A cache in memory, that lasts only in the current R session. #' @param algo The hashing algorithm used for the cache, see #' \code{\link[digest]{digest}} for available algorithms. #' @export cache_memory <- function(algo = "sha512") { cache <- NULL cache_reset <- function() { cache <<- new.env(TRUE, emptyenv()) } cache_set <- function(key, value) { assign(key, value, envir = cache) } cache_get <- function(key) { get(key, envir = cache, inherits = FALSE) } cache_has_key <- function(key) { exists(key, envir = cache, inherits = FALSE) } cache_reset() list( digest = function(...) digest::digest(..., algo = algo), reset = cache_reset, set = cache_set, get = cache_get, has_key = cache_has_key, keys = function() ls(cache) ) } memoise/R/cache_s3.R0000644000175100001440000000343313003701166013722 0ustar hornikusers#' Amazon Web Services S3 Cache #' Amazon Web Services S3 backed cache, for remote caching. #' #' @examples #' #' \dontrun{ #' # Set AWS credentials. #' Sys.setenv("AWS_ACCESS_KEY_ID" = "", #' "AWS_SECRET_ACCESS_KEY" = "") #' #' # Set up a unique bucket name. #' s3 <- cache_s3("unique-bucket-name") #' mem_runif <- memoise(runif, cache = s3) #' } #' #' #' @param cache_name Bucket name for storing cache files. #' @inheritParams cache_memory #' @export cache_s3 <- function(cache_name, algo = "sha512") { if (!(requireNamespace("aws.s3"))) { stop("Package `aws.s3` must be installed for `cache_s3()`.") } # nocov if (!(aws.s3::bucket_exists(cache_name))) { aws.s3::put_bucket(cache_name) # nocov } path <- tempfile("memoise-") dir.create(path) cache_reset <- function() { keys <- cache_keys() lapply(keys, aws.s3::delete_bucket, bucket = cache_name) } cache_set <- function(key, value) { temp_file <- file.path(path, key) on.exit(unlink(temp_file)) saveRDS(value, file = temp_file) aws.s3::put_object(temp_file, object = key, bucket = cache_name) } cache_get <- function(key) { temp_file <- file.path(path, key) httr::with_config(httr::write_disk(temp_file, overwrite = TRUE), { aws.s3::get_object(object = key, bucket = cache_name) }) readRDS(temp_file) } cache_has_key <- function(key) { aws.s3::head_object(object = key, bucket = cache_name) } cache_keys <- function() { items <- lapply(aws.s3::get_bucket(bucket = cache_name), `[[`, "Key") unlist(Filter(Negate(is.null), items)) } list( digest = function(...) digest::digest(..., algo = algo), reset = cache_reset, set = cache_set, get = cache_get, has_key = cache_has_key, keys = cache_keys ) } memoise/R/memoise.R0000644000175100001440000002223413076122254013715 0ustar hornikusers#' \code{mf <- memoise(f)} creates \code{mf}, a memoised copy of #' \code{f}. A memoised copy is basically a #' lazier version of the same function: it saves the answers of #' new invocations, and re-uses the answers of old ones. Under the right #' circumstances, this can provide a very nice speedup indeed. #' #' There are two main ways to use the \code{memoise} function. Say that #' you wish to memoise \code{glm}, which is in the \code{stats} #' package; then you could use \cr #' \code{ mem_glm <- memoise(glm)}, or you could use\cr #' \code{ glm <- memoise(stats::glm)}. \cr #' The first form has the advantage that you still have easy access to #' both the memoised and the original function. The latter is especially #' useful to bring the benefits of memoisation to an existing block #' of R code. #' #' Two example situations where \code{memoise} could be of use: #' \itemize{ #' \item You're evaluating a function repeatedly over the rows (or #' larger chunks) of a dataset, and expect to regularly get the same #' input. #' \item You're debugging or developing something, which involves #' a lot of re-running the code. If there are a few expensive calls #' in there, memoising them can make life a lot more pleasant. #' If the code is in a script file that you're \code{source()}ing, #' take care that you don't just put \cr #' \code{ glm <- memoise(stats::glm)} \cr #' at the top of your file: that would reinitialise the memoised #' function every time the file was sourced. Wrap it in \cr #' \code{ if (!is.memoised(glm)) }, or do the memoisation call #' once at the R prompt, or put it somewhere else where it won't get #' repeated. #' } #' #' @name memoise #' @title Memoise a function. #' @param f Function of which to create a memoised copy. #' @param ... optional variables specified as formulas with no RHS to use as #' additional restrictions on caching. See Examples for usage. #' @param envir Environment of the returned function. #' @param cache Cache function. #' @seealso \code{\link{forget}}, \code{\link{is.memoised}}, #' \code{\link{timeout}}, \url{http://en.wikipedia.org/wiki/Memoization} #' @aliases memoise memoize #' @export memoise memoize #' @importFrom digest digest #' @examples #' # a() is evaluated anew each time. memA() is only re-evaluated #' # when you call it with a new set of parameters. #' a <- function(n) { runif(n) } #' memA <- memoise(a) #' replicate(5, a(2)) #' replicate(5, memA(2)) #' #' # Caching is done based on parameters' value, so same-name-but- #' # changed-value correctly produces two different outcomes... #' N <- 4; memA(N) #' N <- 5; memA(N) #' # ... and same-value-but-different-name correctly produces #' # the same cached outcome. #' N <- 4; memA(N) #' N2 <- 4; memA(N2) #' #' # memoise() knows about default parameters. #' b <- function(n, dummy="a") { runif(n) } #' memB <- memoise(b) #' memB(2) #' memB(2, dummy="a") #' # This works, because the interface of the memoised function is the same as #' # that of the original function. #' formals(b) #' formals(memB) #' # However, it doesn't know about parameter relevance. #' # Different call means different caching, no matter #' # that the outcome is the same. #' memB(2, dummy="b") #' #' # You can create multiple memoisations of the same function, #' # and they'll be independent. #' memA(2) #' memA2 <- memoise(a) #' memA(2) # Still the same outcome #' memA2(2) # Different cache, different outcome #' #' # Don't do the same memoisation assignment twice: a brand-new #' # memoised function also means a brand-new cache, and *that* #' # you could as easily and more legibly achieve using forget(). #' # (If you're not sure whether you already memoised something, #' # use is.memoised() to check.) #' memA(2) #' memA <- memoise(a) #' memA(2) #' # Making a memoized automatically time out after 10 seconds. #' memA3 <- memoise(a, ~{current <- as.numeric(Sys.time()); (current - current %% 10) %/% 10 }) #' memA3(2) #' #' # The timeout function is an easy way to do the above. #' memA4 <- memoise(a, ~timeout(10)) #' memA4(2) #' @importFrom stats setNames memoise <- memoize <- function(f, ..., envir = environment(f), cache = cache_memory()) { f_formals <- formals(args(f)) if(is.memoised(f)) { stop("`f` must not be memoised.", call. = FALSE) } f_formal_names <- names(f_formals) f_formal_name_list <- lapply(f_formal_names, as.name) # memoised_function(...) init_call_args <- setNames(f_formal_name_list, f_formal_names) init_call <- make_call(quote(`_f`), init_call_args) validate_formulas(...) additional <- list(...) memo_f <- eval( bquote(function(...) { called_args <- as.list(match.call())[-1] # Formals with a default default_args <- Filter(function(x) !identical(x, quote(expr = )), as.list(formals())) # That has not been called default_args <- default_args[setdiff(names(default_args), names(called_args))] # Evaluate all the arguments args <- c(lapply(called_args, eval, parent.frame()), lapply(default_args, eval, envir = environment())) hash <- `_cache`$digest(c(body(`_f`), args, lapply(`_additional`, function(x) eval(x[[2L]], environment(x))))) if (`_cache`$has_key(hash)) { res <- `_cache`$get(hash) } else { res <- withVisible(.(init_call)) `_cache`$set(hash, res) } if (res$visible) { res$value } else { invisible(res$value) } }, as.environment(list(init_call = init_call))) ) formals(memo_f) <- f_formals attr(memo_f, "memoised") <- TRUE # This should only happen for primitive functions if (is.null(envir)) { envir <- baseenv() } memo_f_env <- new.env(parent = envir) memo_f_env$`_cache` <- cache memo_f_env$`_f` <- f memo_f_env$`_additional` <- additional environment(memo_f) <- memo_f_env class(memo_f) <- c("memoised", "function") memo_f } make_call <- function(name, args) { stopifnot(is.name(name), is.list(args)) as.call(c(list(name), args)) } #' Return a new number after a given number of seconds #' #' This function will return a number corresponding to the system time and #' remain stable until a given number of seconds have elapsed, after which it #' will update to the current time. This makes it useful as a way to timeout #' and invalidate a memoised cache after a certain period of time. #' @param seconds Number of seconds after which to timeout. #' @param current The current time as a numeric. #' @return A numeric that will remain constant until the seconds have elapsed. #' @seealso \code{\link{memoise}} #' @export #' @examples #' a <- function(n) { runif(n) } #' memA <- memoise(a, ~timeout(10)) #' memA(2) timeout <- function(seconds, current = as.numeric(Sys.time())) { (current - current %% seconds) %/% seconds } validate_formulas <- function(...) { format.name <- function(x, ...) format(as.character(x), ...) is_formula <- function(x) { if (is.call(x) && identical(x[[1]], as.name("~"))) { if (length(x) > 2L) { stop("`x` must be a one sided formula [not ", format(x), "].", call. = FALSE) } } else { stop("`", format(x), "` must be a formula.", call. = FALSE) } } dots <- eval(substitute(alist(...))) lapply(dots, is_formula) } #' @export print.memoised <- function(x, ...) { cat("Memoised Function:\n") tryCatch(print(environment(x)$`_f`), error = function(e) stop("No function defined!", call. = FALSE)) } #' Forget past results. #' Resets the cache of a memoised function. #' #' @param f memoised function #' @export #' @seealso \code{\link{memoise}}, \code{\link{is.memoised}} #' @examples #' memX <- memoise(function() { Sys.sleep(1); runif(1) }) #' # The forget() function #' system.time(print(memX())) #' system.time(print(memX())) #' forget(memX) #' system.time(print(memX())) forget <- function(f) { if (!is.memoised(f)) { return(FALSE) } env <- environment(f) if (!exists("_cache", env, inherits = FALSE)) return(FALSE) # nocovr cache <- get("_cache", env) cache$reset() TRUE } #' Test whether a function is a memoised copy. #' Memoised copies of functions carry an attribute #' \code{memoised = TRUE}, which is.memoised() tests for. #' @param f Function to test. #' @seealso \code{\link{memoise}}, \code{\link{forget}} #' @export is.memoised is.memoized #' @aliases is.memoised is.memoized #' @examples #' mem_lm <- memoise(lm) #' is.memoised(lm) # FALSE #' is.memoised(mem_lm) # TRUE is.memoised <- is.memoized <- function(f) { is.function(f) && inherits(f, "memoised") } #' Test whether a memoised function has been cached for particular arguments. #' @param f Function to test. #' @param ... arguments to function. #' @seealso \code{\link{is.memoised}}, \code{\link{memoise}} #' @export #' @examples #' mem_sum <- memoise(sum) #' has_cache(mem_sum)(1, 2, 3) # FALSE #' mem_sum(1, 2, 3) #' has_cache(mem_sum)(1, 2, 3) # TRUE has_cache <- function(f, ...) { if(!is.memoised(f)) stop("`f` is not a memoised function!", call. = FALSE) # Modify the function body of the function to simply return TRUE and FALSE # rather than get or set the results of the cache body <- body(f) body[[7]] <- quote(if (`_cache`$has_key(hash)) return(TRUE) else return(FALSE)) body(f) <- body f } memoise/README.md0000644000175100001440000000457413076120104013211 0ustar hornikusers# memoise [![Travis-CI Build Status](https://travis-ci.org/hadley/memoise.svg?branch=master)](https://travis-ci.org/hadley/memoise) [![Coverage Status](https://img.shields.io/codecov/c/github/hadley/memoise/master.svg)](https://codecov.io/github/hadley/memoise?branch=master) # Memoization If a function is called multiple times with the same input, you can often speed things up by keeping a cache of known answers that it can retrieve. This is called memoisation . The `memoise` package provides a simple syntax ```r mf <- memoise(f) ``` to create `mf()`, a memoised wrapper around `f()`. You can clear `mf`'s cache with ```r forget(mf) ``` and you can test whether a function is memoised with ```r is.memoised(mf) # TRUE is.memoised(f) # FALSE ``` # Installation ``` devtools::install_github("hadley/memoise") ``` # External Caches `memoise` also supports external caching in addition to the default in-memory caches. * `cache_filesystem()` allows caching using files on a local filesystem. You can point this to a shared file such as dropbox or google drive to share caches between systems. * `cache_s3()` allows caching on [Amazon S3](https://aws.amazon.com/s3/) ## AWS S3 Use `cache_s3()` to cache objects using s3 storage. Requires you to specify a bucket using `cache_name`. When creating buckets, they must be unique among all s3 users when created. ```r Sys.setenv("AWS_ACCESS_KEY_ID" = "", "AWS_SECRET_ACCESS_KEY" = "") mrunif <- memoise(runif, cache = cache_s3("")) mrunif(10) # First run, saves cache mrunif(10) # Loads cache, results should be identical ``` ## Filesystem `cache_filesystem` can be used for a file system cache. This is useful for preserving the cache between R sessions as well as sharing between systems when using a shared or synced files system such as Dropbox or Google Drive. ```r fc <- cache_filesystem("~/.cache") mrunif <- memoise(runif, cache = fc) mrunif(20) # Results stored in local file dbc <- cache_filesystem("~/Dropbox/.rcache") mrunif <- memoise(runif, cache = dbc) mrunif(20) # Results stored in Dropbox .rcache folder which will be synced between computers. gdc <- cache_filesystem("~/Google Drive/.rcache") mrunif <- memoise(runif, cache = gdc) mrunif(20) # Results stored in Google Drive .rcache folder which will be synced between computers. ``` memoise/MD50000644000175100001440000000215013076317216012242 0ustar hornikusers1206465e603904886c34375ed3e4153c *DESCRIPTION 722211a1118fa7bad3f44650438df16f *LICENSE c8048cff1dfa17e590d39dda91fac613 *NAMESPACE 3ac6db7d68d0236b9e246cae78eb5aad *NEWS.md 03970fb1487e5e2661ceefb641a3092c *R/cache_filesystem.R d841451868115d25dadde8698457cfaa *R/cache_memory.R 596de90c02bd1d5c59205afbfa18ba2f *R/cache_s3.R 744002eaba6f98e5e415b3f776953a8f *R/memoise.R b5b1093427ab48ed04390d58dfdd7654 *README.md 2b8d663545955c84410fd43443a6e204 *man/cache_filesystem.Rd 521d9869c3b32561ae749eaa3f91c5a1 *man/cache_memory.Rd 6b256f1905f1bea07e4ed00f6968c8a1 *man/cache_s3.Rd 5b4864d5b27d9a06b0bb64ec6e6773bb *man/forget.Rd cba4d5fd07c64d83b9c6e044e5d39866 *man/has_cache.Rd 24bd09185cedc4fc6ad4a742d645ee31 *man/is.memoised.Rd 9c8367375fa6e4014e2e40ef9b5ff99a *man/memoise.Rd 7173ef6c72af631d5ffbeccf66ec7d1b *man/timeout.Rd c4111f4662e1dada1a4d23df123018f9 *tests/testthat.R 31ae9c0755190a25d14eaa3f3007302b *tests/testthat/helper.R 422bfad7a3cf8713a8dc30f2770ef528 *tests/testthat/test-filesystem.R 99072c677b43fcf6c51c3050d66a52c1 *tests/testthat/test-memoise.R 20058da8fbaa76a0ab08d0e0eb0412d7 *tests/testthat/test-s3.R memoise/DESCRIPTION0000644000175100001440000000204313076317216013441 0ustar hornikusersEncoding: UTF-8 Package: memoise Title: Memoisation of Functions Version: 1.1.0 Authors@R: c( person("Hadley", "Wickham", , "hadley@rstudio.com", role = "aut"), person("Jim", "Hester", , "jim.hester@rstudio.com", role = c("aut", "cre")), person("Kirill", "Müller", , "krlmlr+r@mailbox.org", role = "aut"), person("Daniel", "Cook", , "danielecook@gmail.com", role = "aut")) Description: Cache the results of a function so that when you call it again with the same arguments it returns the pre-computed value. URL: https://github.com/hadley/memoise BugReports: https://github.com/hadley/memoise/issues Imports: digest (>= 0.6.3) Suggests: testthat, aws.s3, httr, covr Additional_repositories: http://cloudyr.github.io/drat License: MIT + file LICENSE RoxygenNote: 6.0.1 NeedsCompilation: no Packaged: 2017-04-20 17:35:47 UTC; jhester Author: Hadley Wickham [aut], Jim Hester [aut, cre], Kirill Müller [aut], Daniel Cook [aut] Maintainer: Jim Hester Repository: CRAN Date/Publication: 2017-04-21 05:54:22 UTC memoise/man/0000755000175100001440000000000012776011327012507 5ustar hornikusersmemoise/man/is.memoised.Rd0000644000175100001440000000124213076170424015207 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/memoise.R \name{is.memoised} \alias{is.memoised} \alias{is.memoized} \title{Test whether a function is a memoised copy. Memoised copies of functions carry an attribute \code{memoised = TRUE}, which is.memoised() tests for.} \usage{ is.memoised(f) } \arguments{ \item{f}{Function to test.} } \description{ Test whether a function is a memoised copy. Memoised copies of functions carry an attribute \code{memoised = TRUE}, which is.memoised() tests for. } \examples{ mem_lm <- memoise(lm) is.memoised(lm) # FALSE is.memoised(mem_lm) # TRUE } \seealso{ \code{\link{memoise}}, \code{\link{forget}} } memoise/man/cache_memory.Rd0000644000175100001440000000062313076170424015430 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cache_memory.R \name{cache_memory} \alias{cache_memory} \title{In Memory Cache} \usage{ cache_memory(algo = "sha512") } \arguments{ \item{algo}{The hashing algorithm used for the cache, see \code{\link[digest]{digest}} for available algorithms.} } \description{ A cache in memory, that lasts only in the current R session. } memoise/man/cache_filesystem.Rd0000644000175100001440000000135213076170424016304 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cache_filesystem.R \name{cache_filesystem} \alias{cache_filesystem} \title{Filesystem Cache} \usage{ cache_filesystem(path, algo = "xxhash64") } \arguments{ \item{path}{Directory in which to store cached items.} \item{algo}{The hashing algorithm used for the cache, see \code{\link[digest]{digest}} for available algorithms.} } \description{ Use a cache on the local filesystem that will persist between R sessions. } \examples{ \dontrun{ # Use with Dropbox db <- cache_filesystem("~/Dropbox/.rcache") mem_runif <- memoise(runif, cache = db) # Use with Google Drive gd <- cache_filesystem("~/Google Drive/.rcache") mem_runif <- memoise(runif, cache = gd) } } memoise/man/timeout.Rd0000644000175100001440000000153113076170424014462 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/memoise.R \name{timeout} \alias{timeout} \title{Return a new number after a given number of seconds} \usage{ timeout(seconds, current = as.numeric(Sys.time())) } \arguments{ \item{seconds}{Number of seconds after which to timeout.} \item{current}{The current time as a numeric.} } \value{ A numeric that will remain constant until the seconds have elapsed. } \description{ This function will return a number corresponding to the system time and remain stable until a given number of seconds have elapsed, after which it will update to the current time. This makes it useful as a way to timeout and invalidate a memoised cache after a certain period of time. } \examples{ a <- function(n) { runif(n) } memA <- memoise(a, ~timeout(10)) memA(2) } \seealso{ \code{\link{memoise}} } memoise/man/has_cache.Rd0000644000175100001440000000111513076170424014670 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/memoise.R \name{has_cache} \alias{has_cache} \title{Test whether a memoised function has been cached for particular arguments.} \usage{ has_cache(f, ...) } \arguments{ \item{f}{Function to test.} \item{...}{arguments to function.} } \description{ Test whether a memoised function has been cached for particular arguments. } \examples{ mem_sum <- memoise(sum) has_cache(mem_sum)(1, 2, 3) # FALSE mem_sum(1, 2, 3) has_cache(mem_sum)(1, 2, 3) # TRUE } \seealso{ \code{\link{is.memoised}}, \code{\link{memoise}} } memoise/man/memoise.Rd0000644000175100001440000000765413076170424014446 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/memoise.R \name{memoise} \alias{memoise} \alias{memoize} \title{Memoise a function.} \usage{ memoise(f, ..., envir = environment(f), cache = cache_memory()) } \arguments{ \item{f}{Function of which to create a memoised copy.} \item{...}{optional variables specified as formulas with no RHS to use as additional restrictions on caching. See Examples for usage.} \item{envir}{Environment of the returned function.} \item{cache}{Cache function.} } \description{ \code{mf <- memoise(f)} creates \code{mf}, a memoised copy of \code{f}. A memoised copy is basically a lazier version of the same function: it saves the answers of new invocations, and re-uses the answers of old ones. Under the right circumstances, this can provide a very nice speedup indeed. } \details{ There are two main ways to use the \code{memoise} function. Say that you wish to memoise \code{glm}, which is in the \code{stats} package; then you could use \cr \code{ mem_glm <- memoise(glm)}, or you could use\cr \code{ glm <- memoise(stats::glm)}. \cr The first form has the advantage that you still have easy access to both the memoised and the original function. The latter is especially useful to bring the benefits of memoisation to an existing block of R code. Two example situations where \code{memoise} could be of use: \itemize{ \item You're evaluating a function repeatedly over the rows (or larger chunks) of a dataset, and expect to regularly get the same input. \item You're debugging or developing something, which involves a lot of re-running the code. If there are a few expensive calls in there, memoising them can make life a lot more pleasant. If the code is in a script file that you're \code{source()}ing, take care that you don't just put \cr \code{ glm <- memoise(stats::glm)} \cr at the top of your file: that would reinitialise the memoised function every time the file was sourced. Wrap it in \cr \code{ if (!is.memoised(glm)) }, or do the memoisation call once at the R prompt, or put it somewhere else where it won't get repeated. } } \examples{ # a() is evaluated anew each time. memA() is only re-evaluated # when you call it with a new set of parameters. a <- function(n) { runif(n) } memA <- memoise(a) replicate(5, a(2)) replicate(5, memA(2)) # Caching is done based on parameters' value, so same-name-but- # changed-value correctly produces two different outcomes... N <- 4; memA(N) N <- 5; memA(N) # ... and same-value-but-different-name correctly produces # the same cached outcome. N <- 4; memA(N) N2 <- 4; memA(N2) # memoise() knows about default parameters. b <- function(n, dummy="a") { runif(n) } memB <- memoise(b) memB(2) memB(2, dummy="a") # This works, because the interface of the memoised function is the same as # that of the original function. formals(b) formals(memB) # However, it doesn't know about parameter relevance. # Different call means different caching, no matter # that the outcome is the same. memB(2, dummy="b") # You can create multiple memoisations of the same function, # and they'll be independent. memA(2) memA2 <- memoise(a) memA(2) # Still the same outcome memA2(2) # Different cache, different outcome # Don't do the same memoisation assignment twice: a brand-new # memoised function also means a brand-new cache, and *that* # you could as easily and more legibly achieve using forget(). # (If you're not sure whether you already memoised something, # use is.memoised() to check.) memA(2) memA <- memoise(a) memA(2) # Making a memoized automatically time out after 10 seconds. memA3 <- memoise(a, ~{current <- as.numeric(Sys.time()); (current - current \%\% 10) \%/\% 10 }) memA3(2) # The timeout function is an easy way to do the above. memA4 <- memoise(a, ~timeout(10)) memA4(2) } \seealso{ \code{\link{forget}}, \code{\link{is.memoised}}, \code{\link{timeout}}, \url{http://en.wikipedia.org/wiki/Memoization} } memoise/man/forget.Rd0000644000175100001440000000107613076170424014266 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/memoise.R \name{forget} \alias{forget} \title{Forget past results. Resets the cache of a memoised function.} \usage{ forget(f) } \arguments{ \item{f}{memoised function} } \description{ Forget past results. Resets the cache of a memoised function. } \examples{ memX <- memoise(function() { Sys.sleep(1); runif(1) }) # The forget() function system.time(print(memX())) system.time(print(memX())) forget(memX) system.time(print(memX())) } \seealso{ \code{\link{memoise}}, \code{\link{is.memoised}} } memoise/man/cache_s3.Rd0000644000175100001440000000146013076170424014445 0ustar hornikusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/cache_s3.R \name{cache_s3} \alias{cache_s3} \title{Amazon Web Services S3 Cache Amazon Web Services S3 backed cache, for remote caching.} \usage{ cache_s3(cache_name, algo = "sha512") } \arguments{ \item{cache_name}{Bucket name for storing cache files.} \item{algo}{The hashing algorithm used for the cache, see \code{\link[digest]{digest}} for available algorithms.} } \description{ Amazon Web Services S3 Cache Amazon Web Services S3 backed cache, for remote caching. } \examples{ \dontrun{ # Set AWS credentials. Sys.setenv("AWS_ACCESS_KEY_ID" = "", "AWS_SECRET_ACCESS_KEY" = "") # Set up a unique bucket name. s3 <- cache_s3("unique-bucket-name") mem_runif <- memoise(runif, cache = s3) } } memoise/LICENSE0000644000175100001440000000006113076153563012741 0ustar hornikusersYEAR: 2010-2017 COPYRIGHT HOLDER: Hadley Wickham