Skip to content
Time-based memoized library for Crystal
Crystal Makefile
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
spec
src
.gitignore
.travis.yml
LICENSE
Makefile
README.md
shard.yml

README.md

memoized.cr Build Status

Time-based memoized library for Crystal.

  • crystal: 0.30.0
dependencies:
  memoized:
    github: maiha/memoized.cr
    version: 0.8.0

Usage

# Instance Creations
Memoized(T).new(proc : -> T)                     # memoize forever
Memoized(T).new(&blk : -> T)                     # memoize forever
Memoized(T).new(proc : -> T, span : Time::Span)  # memoize at most the span
Memoized(T).new(span : Time::Span, &blk : -> T)  # memoize at most the span
Memoized(T).new(proc : -> T, path : String)      # memoize until path is updated
Memoized(T).new(path : String, &blk : -> T)      # memoize until path is updated
Memoized(T).new(proc : -> T, change : Change(U)) # memoize until the value changed
Memoized(T).new(change : -> U, &blk : ->T)       # memoize until the value changed

# Instance Methods
Memoized(T)#get : T          # Returns the cached value, otherwise evaluates blk
Memoized(T)#cache? : T?      # Returns the cached value or nil
Memoized(T)#cached? : Cache? # Returns a snapshot of the cache if exists
Memoized(T)#clear : Nil      # Clears cached value
require "memoized"

msg = Memoized(Int32).new(1.minute) do
  Dir["/tmp/*"].size # some high cost operation
end
msg.get # => 82
msg.get # => 82 (cached at most 1 minute)
msg.get # => 90 (we would get a new data after 1 minute)

Cached info

Memoized#cached? and Memoized#cached return a snapshot of the cache as Memoized::Cache if exists.

Memoized::Cache#value  : T          # Returns the cached value
Memoized::Cache#policy : Policy     # Returns a revocation policy for the cache
Memoized::Cache#at     : Time       # Returns the time when the cache was created
Memoized::Cache#taken  : Time::Span # Returns the time taken to execute the block

# shortcuts
Memoized#cached_at     : Time
Memoized#cached_at?    : Time?
Memoized#cached_taken  : Time::Span
Memoized#cached_taken? : Time::Span?
Memoized#cached_sec    : Float64
Memoized#cached_sec?   : Float64?
Memoized#cached_msec   : Float64
Memoized#cached_msec?  : Float64?
m = Memoized(Int32).new{ 1 }
m.cached?                         # => nil
m.cached                          # raises Memoized::NotCached

m.get                             # => 1
m.cached.at                       # => 2019-08-13 16:11:03.913925000
m.cached.taken.total_milliseconds # => 0.001
m.cached_msec                     # => 0.001 (handy shortcut)

Revocation policy

It can be given at 2nd arg, or 1st arg with &blk.

  • Always : keep forever (this is default)
  • Finite : refresh after given Time::Span
  • Source : refresh after given filename is updated (checked by mtime)
  • Change : refresh after given proc returns different value
def int_adder
  cnt = Atomic(Int32).new(0)
  -> { cnt.add(1); cnt.get }
end
  
always = Memoized(Int32).new(int_adder)
finite = Memoized(Int32).new(int_adder, 1.minute)
source = Memoized(Int32).new(int_adder, "/tmp/file")

always, finite

time always.cache? always.get finite.cache? finite.get
00:00:01 nil 1 nil 1
00:00:02 1 1 1 1
00:01:00 1 1 nil 2
00:01:01 1 1 2 2

source

# (when `/tmp/file` not found)
source.get # => 1
source.get # => 1

# touch /tmp/file
source.get # => 2
source.get # => 2
source.get # => 2

# touch /tmp/file
source.get # => 3
source.get # => 3

# rm /tmp/file
source.get # => 3

change

Change policy keeps expired? logic as Proc, so it can emulate all policies.

source = Memoized(Int32).new(int_adder, "/tmp/file")

# can be rewriten by `Change` policy as follows

mtime_watcher = Memoized::Change(Time).new {
  File.info("/tmp/file").modification_time
}
source = Memoized(Int32).new(int_adder, mtime__watcher)

macros to memoize method

  • int, str can be used to avoid redundant proc format.
def foo
  1
end

# standard way
cached_foo = Memoized(Int32).new(-> { foo })

# with int macro
cached_foo = Memoized.int(foo)
  • TODO: I'd like to write like Memozied(Int32).cache(foo). Is it possible?

Examples

Let's speed up Kemal apps with fragment cache.

before

get "/top" do |env|
  shared_data  = build_shared_data
  dynamic_data = build_dynamic_data
  render "src/views/top.ecr"
end

private def build_shared_data
  ...

after

+shared = Memoized.str(build_shared_data, 1.minute)
+
 get "/top" do |env|
-  shared_data  = build_shared_data
+  shared_data  = shared.get
   dynamic_data = build_dynamic_data
   render "src/views/top.ecr"
2016-12-16 18:02:32 +0900 200 GET /top 97.59ms  # 1st time
2016-12-16 18:02:34 +0900 200 GET /top 578.4µs  # fragment cache!
2016-12-16 18:02:36 +0900 200 GET /top 542.4µs  # fragment cache!

Roadmap

0.8.0

  • take proc as a logic of revocation cache
  • error handling (should be a monad?)

Contributing

  1. Fork it ( https://github.com/maiha/memoized.cr/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors

  • maiha maiha - creator, maintainer
You can’t perform that action at this time.