{"id":13507529,"url":"https://github.com/melpon/memoize","last_synced_at":"2026-02-18T09:42:17.655Z","repository":{"id":39920774,"uuid":"95801120","full_name":"melpon/memoize","owner":"melpon","description":"A method caching macro for elixir using CAS on ETS.","archived":false,"fork":false,"pushed_at":"2025-11-10T19:44:39.000Z","size":87,"stargazers_count":200,"open_issues_count":3,"forks_count":13,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-11-10T21:15:51.026Z","etag":null,"topics":["cache","elixir-lang","memoize"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/memoize","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/melpon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-06-29T17:13:34.000Z","updated_at":"2025-11-10T19:44:43.000Z","dependencies_parsed_at":"2023-12-22T19:24:54.599Z","dependency_job_id":"e530f43c-1cd3-4f82-bd16-faaa73c18b70","html_url":"https://github.com/melpon/memoize","commit_stats":{"total_commits":82,"total_committers":6,"mean_commits":"13.666666666666666","dds":0.07317073170731703,"last_synced_commit":"6c0e4088f85a28556e88d540fd11a1322fcd4422"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/melpon/memoize","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melpon%2Fmemoize","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melpon%2Fmemoize/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melpon%2Fmemoize/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melpon%2Fmemoize/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/melpon","download_url":"https://codeload.github.com/melpon/memoize/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/melpon%2Fmemoize/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29575115,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T08:38:15.585Z","status":"ssl_error","status_checked_at":"2026-02-18T08:38:14.917Z","response_time":162,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","elixir-lang","memoize"],"created_at":"2024-08-01T02:00:35.776Z","updated_at":"2026-02-18T09:42:12.647Z","avatar_url":"https://github.com/melpon.png","language":"Elixir","readme":"# Memoize\n\n[![Module Version](https://img.shields.io/hexpm/v/memoize.svg)](https://hex.pm/packages/memoize)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/memoize/)\n[![Total Download](https://img.shields.io/hexpm/dt/memoize.svg)](https://hex.pm/packages/memoize)\n[![License](https://img.shields.io/hexpm/l/memoize.svg)](https://github.com/melpon/memoize/blob/master/LICENSE)\n[![Last Updated](https://img.shields.io/github/last-commit/melpon/memoize.svg)](https://github.com/melpon/memoize/commits/master)\n\nA memoization macro for Elixir.\n\n\u003e \"In computing, memoization or memoisation is an optimization technique used\n\u003e primarily to speed up computer programs by storing the results of expensive\n\u003e function calls and returning the cached result when the same inputs occur\n\u003e again.\"\n\u003e\n\u003e Source: https://en.wikipedia.org/wiki/Memoization\n\n## Requirement\n\n- Elixir 1.9 or later.\n- Erlang/OTP 21.2 or later.\n\n## Installation\n\nAdd `:memoize` to your `mix.exs` dependencies:\n\n```elixir\ndefp deps do\n  [\n    {:memoize, \"~\u003e 1.4\"}\n  ]\nend\n```\n\n## How to memoize\n\nIf you want to cache a function, `use Memoize` on the module and change `def` to `defmemo`.\n\nFor example:\n\n```elixir\ndefmodule Fib do\n  def fibs(0), do: 0\n  def fibs(1), do: 1\n  def fibs(n), do: fibs(n - 1) + fibs(n - 2)\nend\n```\n\nThis code changes to:\n\n```elixir\ndefmodule Fib do\n  use Memoize\n  defmemo fibs(0), do: 0\n  defmemo fibs(1), do: 1\n  defmemo fibs(n), do: fibs(n - 1) + fibs(n - 2)\nend\n```\n\nIf a function defined by `defmemo` raises an error, the result is not cached and one of waiting processes will call the function.\n\n## Exclusive\n\nA caching function that is defined by `defmemo` is never called in parallel.\n\n```elixir\ndefmodule Calc do\n  use Memoize\n  defmemo calc() do\n    Process.sleep(1000)\n    IO.puts \"called!\"\n  end\nend\n\n# call `Calc.calc/0` in parallel using many processes.\nfor _ \u003c- 1..10000 do\n  Process.spawn(fn -\u003e Calc.calc() end, [])\nend\n\n# but, actually `Calc.calc/0` is called only once.\n```\n\n## Invalidate\n\nIf you want to invalidate cache, you can use `Memoize.invalidate/{0-3}`.\n\n```elixir\n# invalidate a cached value of `Fib.fibs(0)`.\nMemoize.invalidate(Fib, :fibs, [0])\n\n# invalidate all cached values of `Fib.fibs/1`.\nMemoize.invalidate(Fib, :fibs)\n\n# invalidate all cached values of `Fib` module.\nMemoize.invalidate(Fib)\n\n# invalidate all cached values.\nMemoize.invalidate()\n```\n\nNotice: `Memoize.invalidate/{0-2}`'s complexity is linear. Therefore, it takes a long time if `Memoize` has many cached values.\n\n## Caching Partial Arguments\n\nIf you want to cache with partial arguments, use `Memoize.Cache.get_or_run/2` directly.\n\n```elixir\ndefmodule Converter do\n  def convert(unique_key, data) do\n    Memoize.Cache.get_or_run({__MODULE__, :resolve, [unique_key]}, fn -\u003e\n      do_convert(data)\n    end)\n  end\nend\n```\n\n## Cache Strategy\n\nCache strategy is a behaviour to management cached values.\n\nBy default, the caching strategy is `Memoize.CacheStrategy.Default`.\n\nIf you want to change the caching strategy, configure `:cache_strategy` in `:memoize` application.\n\n```elixir\nconfig :memoize,\n  cache_strategy: Memoize.CacheStrategy.Eviction\n```\n\n`memoize` provides below caching strategies.\n\n- `Memoize.CacheStrategy.Default`\n- `Memoize.CacheStrategy.Eviction`\n\n## Cache Strategy - Memoize.CacheStrategy.Default\n\nDefault caching strategy.\nIt provides only simple and fast features.\n\nBasically, cached values are not collected automatically.\nTo collect cached values, call `invalidate/{0-4}`, call `garbage_collect/0` or specify `:expires_in` with `defmemo`.\n\n### Expiration\n\nIf you want to invalidate the cache after a certain period of time, you can use `:expires_in`.\n\n```elixir\ndefmodule Api do\n  use Memoize\n  defmemo get_config(), expires_in: 60 * 1000 do\n    call_external_api()\n  end\nend\n```\n\nThe cached value is invalidated in the first `get_config/0` function call after `expires_in` milliseconds have elapsed.\n\nTo collect expired values, you can use `garbage_collect/0`. It collects all expired values. Its complexity is linear.\n\nThe default value of `:expires_in` is configurable as below:\n\n```elixir\nconfig :memoize,\n  cache_strategy: Memoize.CacheStrategy.Default\n\nconfig :memoize, Memoize.CacheStrategy.Default,\n  expires_in: 600_000 # 10 minutes\n```\n\n## Cache Strategy - Memoize.CacheStrategy.Eviction\n\n`Memoize.CacheStrategy.Eviction` is one of caching strategy.\nIt provides many features, but slower than `Memoize.CacheStrategy.Default`.\n\nThe strategy is, basically, if cached memory size is exceeded `max_threshold`, *unused* cached values are collected until memory size falls below `min_threshold`.\n\nTo use `Memoize.CacheStrategy.Eviction`, configure `:cache_strategy` as below:\n\n```elixir\nconfig :memoize,\n  cache_strategy: Memoize.CacheStrategy.Eviction\n\nconfig :memoize, Memoize.CacheStrategy.Eviction,\n  min_threshold: 5_000_000,\n  max_threshold: 10_000_000\n```\n\n### Permanently\n\nIf `:permanent` option is specified with `defmemo`, the value won't be collected automatically.\nIf you want to remove the value, call `invalidate/{0-3}`.\n\n```elixir\ndefmodule Json do\n  use Memoize\n  defmemo get_json(filename), permanent: true do\n    filename |\u003e File.read!() |\u003e Poison.decode!()\n  end\nend\n```\n\nNotice the permanent value includes in used memory size. So you should adjust `min_threshold` value.\n\n### Expiration\n\nIf `:expires_in` option is specified with `defmemo`, the value will be collected after `:expires_in` milliseconds.\nTo be exact, when the `read/3` function is called with any arguments, all expired values will be collected.\n\n```elixir\ndefmodule Api do\n  use Memoize\n  defmemo get_config(), expires_in: 60 * 1000 do\n    call_external_api()\n  end\nend\n```\n\nYou can both specify `:permanent` and `:expires_in`.\nIn the case, the cached value is not collected by `garbage_collect/0` or memory size that exceed `max_threshold`, but after `:expires_in` milliseconds it is collected.\n\n## Cache Strategy - Your Strategy\n\nYou can customize caching strategy.\n\n```elixir\ndefmodule Memoize.CacheStrategy do\n  @callback init() :: any\n  @callback tab(any) :: atom\n  @callback cache(any, any, Keyword.t) :: any\n  @callback read(any, any, any) :: :ok | :retry\n  @callback invalidate() :: integer\n  @callback invalidate(any) :: integer\n  @callback garbage_collect() :: integer\nend\n```\n\nIf you want to use a customized caching strategy, implement `Memoize.CacheStrategy` behaviour.\n\n```elixir\ndefmodule YourAwesomeApp.ExcellentCacheStrategy do\n  @behaviour Memoize.CacheStrategy\n\n  def init() do\n    ...\n  end\n\n  ...\nend\n```\n\nThen, configure `:cache_strategy` in `:memoize` application.\n\n```elixir\nconfig :memoize,\n  cache_strategy: YourAwesomeApp.ExcellentCacheStrategy\n```\n\nNotice `tab/1`, `read/3`, `invalidate/{0-1}`, `garbage_collect/0` are called concurrently.\n`cache/3` is not called concurrently, but other functions are called concurrently while `cache/3` is called by a process.\n\n### init/0\n\nWhen application is started, `init/0` is called only once.\n\n### tab/1\n\nTo determine which ETS tab to use, Memoize calls `tab/0`.\n\n### cache/3\n\nWhen new value is cached, `cache/3` will be called.\nThe first argument is `key` that is used as cache key.\nThe second argument is `value` that is calculated value by cache key.\nThe third argument is `opts` that is passed by `defmemo`.\n\n`cache/3` can return an any value that is called `context`.\n`context` is stored to ETS.\nAnd then, the context is passed to `read/3`'s third argument.\n\n### read/3\n\nWhen a value is looked up by a key, `read/3` will be called.\nfirst and second arguments are same as `cache/3`.\nThe third argument is `context` that is created at `cache/3`.\n\n`read/3` can return `:retry` or `:ok`.\nIf `:retry` is returned, retry the lookup.\nIf `:ok` is returned, return the `value`.\n\n### invalidate/{0,1}\n\nThese functions are called from `Memoize.invalidate/{0-4}`.\n\n### garbage_collect/0\n\nThe function is called from `Memoize.garbage_collect/0`.\n\n## Waiter config\n\nNormally, waiter processes are waiting at the end of the computing process using message passing. However, As the number of waiting processes increases, memory is consumed, so we limit this number of the waiters.\n\nNumber of waiter processes receiving message passing are configured as `config.exs` or `defmemo` opts. (prior `defmemo`).\n\nWith `config.exs`:\n\n```elixir\nconfig :memoize,\n  max_waiter: 100,\n  waiter_sleep_ms: 1000\n```\n\nWith `defmemo` opts:\n\n```elixir\ndefmemo foo(), max_waiter: 100, waiter_sleep_ms: 1000 do\n  ...\nend\n```\n\n- `:max_waiters`: Number of waiter processes receiving message passing. (default: 20)\n- `:waiter_sleep_ms`: Time to sleep when the number of waiter processes exceeds `:max_waiters`. (default: 200)\n\n## Internal\n\n`Memoize` is using CAS (compare-and-swap) on ETS.\n\nCAS is [now available](http://erlang.org/doc/man/ets.html#select_replace-2) in Erlang/OTP 20.\n\n## License\n\nCopyright (c) 2017 melpon\n\nThis library is MIT licensed. See the [LICENSE](https://github.com/melpon/memoize/blob/master/LICENSE)\nfor details.\n","funding_links":[],"categories":["Caching"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmelpon%2Fmemoize","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmelpon%2Fmemoize","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmelpon%2Fmemoize/lists"}