# Can I get away with memoizing a :foldable method?

**URL:** https://discourse.julialang.org/t/can-i-get-away-with-memoizing-a-foldable-method/136951
**Category:** General Usage
**Tags:** memoize, effects, compile-time
**Created:** [May 1, 2026, 3:48pm UTC](https://discourse.julialang.org/t/can-i-get-away-with-memoizing-a-foldable-method/136951 "2026-05-01T15:48:59Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [May 1, 2026, 3:48pm UTC](https://discourse.julialang.org/t/can-i-get-away-with-memoizing-a-foldable-method/136951/1 "2026-05-01T15:48:59Z")

</div>

Starting to dabble in moving long calls from runtime into compile-time, and I’m finding that repeated calls in compile-time are also annoying. Naively, I tried compile-time memoization, also avoiding cache access overhead or type instability at runtime. A base Julia-only toy example:

```julia-auto
# typical type instability in memoization packages
# NOT thread-safe despite intended consistency
const _minusoneto = IdDict{Any, Any}() 

# NOT thread-safe because global state is not
Base.@assume_effects :foldable function minusoneto(n::Int)
  if n < 0
    throw(DomainError(n, "minusoneto only supports nonnegative integer exponents"))
  elseif haskey(_minusoneto, n)
    return _minusoneto[n] # ::Any
  else
    x = 1
    for _ in 1:n x *= -1 end
    _minusoneto[n] = x
    return x
  end
end
foo(::Val{N}) where N = minusoneto(N)
bar(::Val{N}) where N = 10*minusoneto(N)

```

Memoization improves compile times for different callers that repeat a call. For anyone who tries this example, note that `@time @eval` is needed to capture JIT compile times for `:foldable` callees. This example’s callers optimize to constant return values, so I just time `precompile`:

```julia-auto
julia> @time precompile(foo, (Val{123456789012},))
  4.154013 seconds (36.21 k allocations: 1.743 MiB, 99.99% compilation time)
true

julia> @time precompile(bar, (Val{123456789012},))
  0.000127 seconds (232 allocations: 11.578 KiB, 83.42% compilation time)
true

julia> @time precompile(bar, (Val{123456789013},))
  4.129439 seconds (232 allocations: 11.656 KiB, 100.00% compilation time)
true

julia> empty!(_minusoneto); # often need this when minusoneto is redefined

julia> @time precompile(foo, (Val{123456789013},))
  4.139190 seconds (170 allocations: 8.641 KiB, 100.00% compilation time)
true

```

`:foldable` is a sum of these effects:

- `:consistent`
- `:effect_free`
- `:terminates_globally`
- `:noub`
- `:nortcall`

I think `minusoneto` simply satisfies the last 3, and I think it is `:consistent` as long as the cache strictly follows the method’s results, not the other way around on any level. It doesn’t satisfy the documented requirements for `:effect_free` because it’s mutating an externally heap-allocated object, but I can’t imagine how this can be a problem if said object only caches a consistent method (or potentially several within a world age). Did I stumble into an undocumented exception, or am I missing a danger here?
