Packages

Memoization (caching) of function calls

Current section

Files

Jump to
meme lib meme.ex
Raw

lib/meme.ex

defmodule Meme do
#
# public macro to use instead of def
#
defmacro defmemo(def_ast, [timeout: timeout], [do: body]) do
quote do
defmemo(unquote(def_ast), [timeout: unquote(timeout), condition: fn(_) -> true end], [do: unquote(body)])
end
end
defmacro defmemo({:when, when_meta, [{name, meta, raw_args} | guards]}, [timeout: timeout, condition: condition], [do: body]) do
{arg_names, decorated_args} = decorate_args(raw_args)
quote do
def unquote({:when, when_meta, [{name, meta, decorated_args} | guards]}) do
unquote(meme_under_the_hood(name, arg_names, timeout, body, condition))
end
end
end
defmacro defmemo({name, meta, raw_args}, [timeout: timeout, condition: condition], [do: body]) do
{arg_names, decorated_args} = decorate_args(raw_args)
quote do
def unquote({name, meta, decorated_args}) do
unquote(meme_under_the_hood(name, arg_names, timeout, body, condition))
end
end
end
#
# public macro - creates cached copy of function
# if function name is :foo then two functions will be generated
# :foo and :cached_foo (arity, arguments and business logic is the same)
#
defmacro defcached(def_ast, [timeout: timeout], [do: body]) do
quote do
defcached(unquote(def_ast), [timeout: unquote(timeout), condition: fn(_) -> true end], [do: unquote(body)])
end
end
defmacro defcached(raw_definition = {:when, when_meta, [{name, meta, raw_args} | guards]}, [timeout: timeout, condition: condition], [do: body]) do
{arg_names, decorated_args} = decorate_args(raw_args)
cached_name = String.to_atom("cached_#{name}")
quote do
def unquote({:when, when_meta, [{cached_name, meta, decorated_args} | guards]}) do
unquote(meme_under_the_hood(cached_name, arg_names, timeout, body, condition))
end
def unquote(raw_definition) do
unquote(body)
end
end
end
defmacro defcached(raw_definition = {name, meta, raw_args}, [timeout: timeout, condition: condition], [do: body]) do
{arg_names, decorated_args} = decorate_args(raw_args)
cached_name = String.to_atom("cached_#{name}")
quote do
def unquote({cached_name, meta, decorated_args}) do
unquote(meme_under_the_hood(cached_name, arg_names, timeout, body, condition))
end
def unquote(raw_definition) do
unquote(body)
end
end
end
#
# public macro to use instead of defp
#
defmacro defmemop(def_ast, [timeout: timeout], [do: body]) do
quote do
defmemop(unquote(def_ast), [timeout: unquote(timeout), condition: fn(_) -> true end], [do: unquote(body)])
end
end
defmacro defmemop({:when, when_meta, [{name, meta, raw_args} | guards]}, [timeout: timeout, condition: condition], [do: body]) do
{arg_names, decorated_args} = decorate_args(raw_args)
quote do
defp unquote({:when, when_meta, [{name, meta, decorated_args} | guards]}) do
unquote(meme_under_the_hood(name, arg_names, timeout, body, condition))
end
end
end
defmacro defmemop({name, meta, raw_args}, [timeout: timeout, condition: condition], [do: body]) do
{arg_names, decorated_args} = decorate_args(raw_args)
quote do
defp unquote({name, meta, decorated_args}) do
unquote(meme_under_the_hood(name, arg_names, timeout, body, condition))
end
end
end
#
# public macro - creates functions like defcached do, but private
#
defmacro defcachedp(def_ast, [timeout: timeout], [do: body]) do
quote do
defcachedp(unquote(def_ast), [timeout: unquote(timeout), condition: fn(_) -> true end], [do: unquote(body)])
end
end
defmacro defcachedp(raw_definition = {:when, when_meta, [{name, meta, raw_args} | guards]}, [timeout: timeout, condition: condition], [do: body]) do
{arg_names, decorated_args} = decorate_args(raw_args)
cached_name = String.to_atom("cached_#{name}")
quote do
defp unquote({:when, when_meta, [{cached_name, meta, decorated_args} | guards]}) do
unquote(meme_under_the_hood(cached_name, arg_names, timeout, body, condition))
end
defp unquote(raw_definition) do
unquote(body)
end
end
end
defmacro defcachedp(raw_definition = {name, meta, raw_args}, [timeout: timeout, condition: condition], [do: body]) do
{arg_names, decorated_args} = decorate_args(raw_args)
cached_name = String.to_atom("cached_#{name}")
quote do
defp unquote({cached_name, meta, decorated_args}) do
unquote(meme_under_the_hood(cached_name, arg_names, timeout, body, condition))
end
defp unquote(raw_definition) do
unquote(body)
end
end
end
#
# simple public function for MFA usage
#
def memo(module, func, args, ttl) when (is_atom(module) and is_atom(func) and is_list(args) and is_integer(ttl) and (ttl > 0)) do
key = {module, func, args}
case Cachex.get(:meme, key) do
{:missing, nil} ->
value = :erlang.apply(module, func, args)
{:ok, true} = Cachex.set(:meme, key, value, [ttl: ttl])
value
{:ok, value} ->
value
end
end
#
# priv boilerplate for defmemo / defmemop macro
#
defp meme_under_the_hood(name, args, timeout, code, condition) do
conditional_caching_ast =
condition
|> case do
{:fn, _, [{:->, _, [[{:_, _, Meme}], true]}]} ->
quote do
{:ok, true} = Cachex.set(:meme, key, value, [ttl: unquote(timeout)])
end
_ ->
quote do
if ((unquote(condition)).(value) == true) do
{:ok, true} = Cachex.set(:meme, key, value, [ttl: unquote(timeout)])
end
end
end
quote do
(
key = {__MODULE__, unquote(name), unquote(args)}
case Cachex.get(:meme, key) do
{:missing, nil} ->
value = (unquote(code))
unquote(conditional_caching_ast)
value
{:ok, value} ->
value
end
)
end
end
defp decorate_args(raw_args) when is_list(raw_args) do
raw_args
|> Stream.with_index
|> Stream.map(fn({arg_ast, index}) ->
arg_name = Macro.var(:"arg#{index}", __MODULE__)
{
arg_name,
quote do
unquote(arg_ast) = unquote(arg_name)
end
}
end)
|> Enum.unzip
end
end