Creates a memoized function from a named or anonymous function. Calls to the memoized function are served from cache after the first execution for a given key.
Runtime controls:
memo.force = TRUE: ignore an existing cached value and recompute
memo.dryrun = TRUE: do not execute or cache; return TRUE if execution would occur, FALSE if a cache hit exists
By default, NULL results are not cached. Set allow.null = TRUE to
cache NULL values as valid outcomes.
Hashing strategy for stored values:
id component: explicit id when provided, otherwise an inferred function name when available
function component: hash of the function formals and body (or function_hash_override when supplied)
call component: hash of normalized call arguments (including defaulted arguments, ordered by argument name)
The final storage key is a hash over these three components. This means that, by default, changing a function's implementation invalidates old cache entries for future reads while leaving historical values in storage.
Matching keys only share cached values when memo calls use the same underlying storage. Separate in-memory memo instances do not share values, even when keys are identical.
memo(f, id = NULL, function_hash_override = NULL, allow.null = FALSE)function to memoise
optional identifier used to scope cache keys; defaults to the function name when available
optional override value used in place of the
default function identity hash; can be used with explicit or inferred
id
if TRUE, the memoized function caches NULL
results; FALSE by default
the memoed function
library(magrittr)
# --- Basic memoization -----------------------------------------------------
# a simple function
simple.function <- function (value) {
print("Executing!")
value
}
# memoize and keep the new function separate
simple.function.memo <- memo(simple.function)
# id defaults to the function name when available
attr(simple.function.memo, "memo.id")
#> [1] "simple.function"
# memoize in place
simple.function %<>% memo()
# memoize anonymous functions
simple.function2 <- (function (value) value) %>% memo()
# use an explicit id for anonymous or generated functions
anonymous.memo <- memo(function (value) value, id = "example/anon")
# use an explicit id to keep cache stable across renames or wrappers
# start from the original non-memoized function to avoid wrapping a memo twice
renamed.function <- function (value) {
print("Executing!")
value
}
renamed.memo <- memo(renamed.function, id = "simple.function")
# first call executes
simple.function(10)
#> [1] "Executing!"
#> [1] 10
# second call with same inputs is cached
simple.function(10)
#> [1] 10
# different inputs execute once, then cache
simple.function(20)
#> [1] "Executing!"
#> [1] 20
simple.function(20)
#> [1] 20
# --- Runtime controls ------------------------------------------------------
# force recomputation even if cached
simple.function(10, memo.force = TRUE)
#> [1] "Executing!"
#> [1] 10
# dry-run does not execute or write; it reports whether execution would occur
simple.function(30, memo.dryrun = TRUE) # TRUE (not cached yet)
#> [1] TRUE
simple.function(30) # executes and caches
#> [1] "Executing!"
#> [1] 30
simple.function(30, memo.dryrun = TRUE) # FALSE (already cached)
#> [1] FALSE
# --- NULL handling ---------------------------------------------------------
# by default NULL results are not cached
null.default <- memo(function (value) {
print("Executing NULL function")
NULL
})
null.default(1)
#> [1] "Executing NULL function"
#> NULL
null.default(1)
#> [1] "Executing NULL function"
#> NULL
# allow.null = TRUE caches NULL values too
null.cached <- memo(function (value) {
print("Executing NULL function with allow.null")
NULL
}, allow.null = TRUE)
null.cached(1)
#> [1] "Executing NULL function with allow.null"
#> NULL
null.cached(1)
#> NULL
# --- Function hash override ------------------------------------------------
# use persistent storage to demonstrate cache reuse across separate memo instances
base.dir <- file.path(tempdir(), "memofunc-example-function-hash")
unlink(base.dir, recursive = TRUE, force = TRUE)
old.options <- options(memofunc.storage.provider = list(name = "file", base.dir = base.dir))
counter <- new.env(parent = emptyenv())
counter$executed <- 0
f.v1 <- function (value) {
counter$executed <- counter$executed + 1
value
}
f.v2 <- function (value) {
counter$executed <- counter$executed + 1
value + 1
}
# default behavior: changed body creates a new function hash component
memo(f.v1, id = "shared-id")(100)
#> [1] 100
counter$executed <- 0
memo(f.v2, id = "shared-id")(100)
#> [1] 101
counter$executed
#> [1] 1
# override: both implementations share function hash component intentionally
memo(f.v1, id = "shared-id", function_hash_override = "stable-token")(200)
#> [1] 200
counter$executed <- 0
memo(f.v2, id = "shared-id", function_hash_override = "stable-token")(200)
#> [1] 200
counter$executed
#> [1] 0
# this reuse only works because both memos share the same file-backed store above
# with separate in-memory memo instances, matching keys still would not share values
options(old.options)
unlink(base.dir, recursive = TRUE, force = TRUE)
# --- Performance example ---------------------------------------------------
slow.function <- (function (value) Sys.sleep(value)) %>% memo(allow.null = TRUE)
system.time(slow.function(2))
#> user system elapsed
#> 0.002 0.000 2.004
system.time(slow.function(2))
#> user system elapsed
#> 0.001 0.000 0.001