//// This is a simple library that allows you to define lazily initialised //// constants, without the usual restrictions around constants in Gleam. //// Examples where this might be useful include only compiling regexes once, //// reading environment variables, or caching and re-using options constructed //// using the builder pattern. //// //// Constants are stored using //// [persistent terms](https://www.erlang.org/doc/apps/erts/persistent_term.html) //// in Erlang, and using a global //// [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) //// in Javascript. Make sure you read the documentation for your //// target platform to understand the performance implications. //// //// This library can easily be overused! Only reach for it if you tested and //// measured that it makes a noticable difference! Usually, constructing //// constants is very cheap, even if it looks like duplicate work. Both targets //// already include optimisations to make this faster. In particular, every //// access of a constant defined using `lazy_const` also includes the use of //// a type constructor and a bunch of function calls. //// //// ### Type Safety //// //// Always create a new top-level wrapper function for each lazy constant. //// The first line of your constant must always look like this, for a constant //// named `my_constant`: //// //// ```gleam //// use <- lazy_const.new(lazy_const.defined_in(my_constant)) //// // ... //// ``` //// //// The first line must always _use_ `lazy_const.new`, and must call //// `lazy_const.defined_in`, passing the outer wrapping function. //// //// The outer wrapper function passed as the first argument is used as the key //// inside the cache structure, so a constant is only unique if that key doesn't //// change, and there can only ever be one constant per `Id` argument. //// //// Functions defined at the top-level are guaranteed to be constant values. //// If a locally defined function is used, a new constant might be created for //// every call, depending on the target and the currently active optimisations, //// degrading performance and using up more and more memory on each call. //// //// //// ### Example usage //// //// ``` //// import gleam/regex //// // only ever compile this regex once //// fn alphanum_re() -> regex.Regex { //// use <- lazy_const.new(lazy_const.defined_in(alphanum_re)) //// let assert Ok(re) = regex.from_string("[a-zA-Z0-9]+") //// re //// } //// //// // an expensive calculation that should only be done once. //// fn fib40() -> Int { //// use <- lazy_const.new(lazy_const.defined_in(fib40)) //// fib(40) //// } //// //// fn fib(n: Int) -> Int { //// case n { //// 1 -> 1 //// 2 -> 1 //// n -> fib(n - 1) + fib(n - 2) //// } //// } //// ``` //// //// Always prefer few big constants over many small ones. //// //// ### Limitations //// //// You cannot construct cyclical values using this library - calling a //// constant inside of its definition will still lead to a stack overflow. //// //// Constants have to be defined as top-level functions and cannot take any //// arguments. //// //// The target (in particular Erlang) may put additional limits on the amount //// constants defined, and performance may degrade for every additional constant. //// //// While in in most cases, the constructor is only ever called once, this should //// shoul not be relied upon without additional safety nets - the target is in //// theory allowed to evict constants at any time. Every node in a cluster will //// also have its own instance of a constant. /// A type used to make sure you don't accidentily pass the constructor and /// wrapper functions the wrong order! pub opaque type Id(a) { Id(inner: fn() -> a) } /// Explicitely mark a function as the wrapping function where the constant is /// defined in. /// /// Read the module documentation at the top to learn more about how this /// library works and how to use it! pub fn defined_in(outer wrapper_function: fn() -> a) -> Id(a) { Id(wrapper_function) } /// Define a new lazy constant. The `ctor` will only be called once, after which /// the returned value will be cached, such that future invocations are fast! /// /// **Warning:** Please make sure you have read the docs at the top of the module first. pub fn new(in wrapper_function: Id(a), ctor inner_fn: fn() -> a) -> a { do_new(wrapper_function.inner, inner_fn) } @external(erlang, "lazy_const_ffi", "new") @external(javascript, "./lazy_const_ffi.mjs", "default") fn do_new(_outer: fn() -> a, inner: fn() -> a) -> a { inner() }