ShotDs.Stt.TermFactory (shot_ds v1.0.0)

Copy Markdown View Source

Contains functionality of creating, memoizing and accessing terms using an ETS cache.

In HOL, terms often share sub-expressions, meaning they form Directed Acyclic Graphs (DAGs) rather than simple abstract syntax trees (ASTs). Hence, representing terms as nested sturctures has the big disadvantage of needing to store the same sub-expression multiple times in memory. ETS, the Erlang term storage offers an efficient caching mechanism which we can utilize to ensure that a specific term is created exactly once. Furthermore, Elixir's immutability ensures pointers to the terms being static, i.e., a term can not be altered once it is memoized.

Error Handling

All functions that can fail return a tuple {:ok, result} or {:error, reason} per default to facilitate error handling. These functions also come in a "bang" variant (with an "!") that raises errors instead of propagating that. This behavior can be useful for quick prototyping or when it is guaranteed that the function calls will succeed.

Note

Consider using the more expressive API defined in ShotDs.Hol.Dsl ontop of this module.

Summary

Term Cache

Looks up and returns the concrete ShotDs.Data.Term struct for the given ID.

Looks up and returns the concrete ShotDs.Data.Term struct for the given ID, erroring out if the ID doesn't exist.

Memoizes the given term in the module's :ets table. Terms will be identified if they share the same signature, e.g., all fields but id.

Term Scratchpad

Commits the term with the given ID (which might be local) to the global ETS table. Returns a tuple {:ok, new_global_id} or {:error, reason}.

Commits the term with the given ID (which might be local) to the global ETS table. Returns the new global ID, raising on errors.

Creates a local "scratchpad" ETS table for the current process. Can be used to cleanup temporary free variable terms e.g., when creating abstraction terms.

Destroys the processes ephemeral ETS table explicitly.

Wraps the given function and executes it with an active scratchpad.

Wraps the given function and executes it with an active scratchpad, propagating errors when they occur.

Term Construction API

Applies the term corresponding to head_id to the list of terms corresponding to arg_ids.

Applies the term corresponding to head_id to the list of terms corresponding to arg_ids, folding left to right, erroring out if one of the terms does not exist.

Abstracts the term corresponding to the given id over the given variable. If the variable is already bound, adds it to the list of bound variables.

Abstracts the term corresponding to the given id over the given variable (or variable term), erroring out if either term does not exist or the given variable term is not primitive. If the variable is already bound, adds it to the list of bound variables.

Applies the term corresponding to left_id to the term corresponding to right_id.

Applies the term corresponding to left_id to the term corresponding to right_id, erroring out if either term does not exist or their types are incompatible.

Creates a constant with the corresponding name and type and returns the ID for its term representation. Short for ShotDs.Data.Declaration.new_const(name, type) |> make_term().

Creates a free variable with the corresponding name and type and returns the ID for its term representation. Short for ShotDs.Data.Declaration.new_free_var(name, type) |> make_term().

Creates a fresh constant of the given type and returns the ID for its term representation. Short for ShotDs.Data.Declaration.fresh_const(type) |> make_term().

Creates a fresh variable of the given type and returns the ID for its term representation. Short for ShotDs.Data.Declaration.fresh_var(type) |> make_term().

Creates and memoizes a term representing a single free variable, bound variable or constant. Handles eta-expansion automatically.

Checks whether the term corresponding to the given ID is a primitive term i.e., if it is an eta-expanded constant or variable.

Types

There are two error scenarios when looking up a term via its ID. The ID might not correspond to ShotDs.Data.Term.global_term_id() or ShotDs.Data.Term.local_term_id(). If it does, it may not be pointing to any term. Lastly, the ID can be a local ID, i.e., points to a sketchpad, but no sketchpad may be active for the current process.

Term Cache

get_term(id)

@spec get_term(ShotDs.Data.Term.term_id()) ::
  {:ok, ShotDs.Data.Term.t()} | lookup_error_t()

Looks up and returns the concrete ShotDs.Data.Term struct for the given ID.

Routes to global or local ETS table based on the sign of the ID.

get_term!(id)

Looks up and returns the concrete ShotDs.Data.Term struct for the given ID, erroring out if the ID doesn't exist.

Routes to global or local ETS table based on the sign of the ID.

memoize(draft_term)

Memoizes the given term in the module's :ets table. Terms will be identified if they share the same signature, e.g., all fields but id.

Returns the looked up or generated ID of the term. IDs are generated as integers in a concurrency-safe way. Local IDs are represented by negative, global ones by positive integers.

Note

If a scratchpad is active, the term is written to the local ETS table. Otherwise, it writes globally.

Example:

iex> id = memoize(t)
iex> match?({:ok, %{^t | id: ^id}}, get_term(id))
true

Term Scratchpad

commit_to_global(id)

Commits the term with the given ID (which might be local) to the global ETS table. Returns a tuple {:ok, new_global_id} or {:error, reason}.

Also ensures recursively that the arguments are memoized.

commit_to_global!(id)

Commits the term with the given ID (which might be local) to the global ETS table. Returns the new global ID, raising on errors.

Also ensures recursively that the arguments are memoized.

start_scratchpad()

@spec start_scratchpad() :: :ok

Creates a local "scratchpad" ETS table for the current process. Can be used to cleanup temporary free variable terms e.g., when creating abstraction terms.

Puts the reference to the ETS table in the processes memory under the :term_scratchpad key.

stop_scratchpad()

@spec stop_scratchpad() :: :ok

Destroys the processes ephemeral ETS table explicitly.

Note

The scratchpad ETS table also dies with the process automatically.

with_scratchpad(fun)

@spec with_scratchpad((-> {:ok, ShotDs.Data.Term.term_id()} | {:error, t})) ::
  {:ok, ShotDs.Data.Term.term_id()} | {:error, t}
when t: term()

Wraps the given function and executes it with an active scratchpad.

Commits the final result to the global ETS table Cleans up the scratchpad afterwards if it didn't exist previously. Acts as a concurrency-safe garbage collector.

with_scratchpad!(fun)

@spec with_scratchpad!((-> ShotDs.Data.Term.term_id())) :: ShotDs.Data.Term.term_id()

Wraps the given function and executes it with an active scratchpad, propagating errors when they occur.

Commits the final result to the global ETS table Cleans up the scratchpad afterwards if it didn't exist previously. Acts as a concurrency-safe garbage collector.

Term Construction API

fold_apply(head_id, arg_ids)

Applies the term corresponding to head_id to the list of terms corresponding to arg_ids.

fold_apply!(head_id, arg_ids)

Applies the term corresponding to head_id to the list of terms corresponding to arg_ids, folding left to right, erroring out if one of the terms does not exist.

make_abstr_term(term_id, var)

@spec make_abstr_term(
  ShotDs.Data.Term.term_id(),
  ShotDs.Data.Declaration.t() | ShotDs.Data.Term.term_id()
) ::
  {:ok, ShotDs.Data.Term.term_id()}
  | lookup_error_t()
  | {:error, :var_term_not_primitive}

Abstracts the term corresponding to the given id over the given variable. If the variable is already bound, adds it to the list of bound variables.

Note

Consider using ShotDs.Hol.Dsl.lambda/2 instead as it is more expressive and robust.

make_abstr_term!(term_id, var)

Abstracts the term corresponding to the given id over the given variable (or variable term), erroring out if either term does not exist or the given variable term is not primitive. If the variable is already bound, adds it to the list of bound variables.

Note

Consider using ShotDs.Hol.Dsl.lambda/2 instead as it is more expressive and robust.

make_appl_term(left_id, right_id)

@spec make_appl_term(ShotDs.Data.Term.term_id(), ShotDs.Data.Term.term_id()) ::
  {:ok, ShotDs.Data.Term.term_id()}
  | lookup_error_t()
  | {:error, :incompatible_types}

Applies the term corresponding to left_id to the term corresponding to right_id.

Note

Consider using ShotDs.Hol.Dsl.app/2 instead as it is more expressive and robust.

make_appl_term!(left_id, right_id)

Applies the term corresponding to left_id to the term corresponding to right_id, erroring out if either term does not exist or their types are incompatible.

Note

Consider using ShotDs.Hol.Dsl.app/2 instead as it is more expressive and robust.

make_const_term(name, type)

@spec make_const_term(String.t() | reference(), ShotDs.Data.Type.t()) ::
  ShotDs.Data.Term.term_id()

Creates a constant with the corresponding name and type and returns the ID for its term representation. Short for ShotDs.Data.Declaration.new_const(name, type) |> make_term().

make_free_var_term(name, type)

@spec make_free_var_term(String.t() | reference(), ShotDs.Data.Type.t()) ::
  ShotDs.Data.Term.term_id()

Creates a free variable with the corresponding name and type and returns the ID for its term representation. Short for ShotDs.Data.Declaration.new_free_var(name, type) |> make_term().

make_fresh_const_term(type)

@spec make_fresh_const_term(ShotDs.Data.Type.t()) :: ShotDs.Data.Term.term_id()

Creates a fresh constant of the given type and returns the ID for its term representation. Short for ShotDs.Data.Declaration.fresh_const(type) |> make_term().

make_fresh_var_term(type)

@spec make_fresh_var_term(ShotDs.Data.Type.t()) :: ShotDs.Data.Term.term_id()

Creates a fresh variable of the given type and returns the ID for its term representation. Short for ShotDs.Data.Declaration.fresh_var(type) |> make_term().

Note

Consider wrapping functions using this to create temporary free variables with with_scratchpad/1 or with_scratchpad!/1 for garbage collection.

make_term(decl)

Creates and memoizes a term representing a single free variable, bound variable or constant. Handles eta-expansion automatically.

Returns a local ID if a scratchpad is active in the caller's process.

Example:

iex> co = ShotDs.Data.Declaration.fresh_const(Type.new(:o))
iex> id = make_term(co)

primitive_term?(term_id)

@spec primitive_term?(ShotDs.Data.Term.term_id()) ::
  {:ok, boolean()} | lookup_error_t()

Checks whether the term corresponding to the given ID is a primitive term i.e., if it is an eta-expanded constant or variable.

Types

lookup_error_t()

@type lookup_error_t() ::
  {:error, :invalid_id}
  | {:error, :non_existing_id}
  | {:error, :scratchpad_missing}

There are two error scenarios when looking up a term via its ID. The ID might not correspond to ShotDs.Data.Term.global_term_id() or ShotDs.Data.Term.local_term_id(). If it does, it may not be pointing to any term. Lastly, the ID can be a local ID, i.e., points to a sketchpad, but no sketchpad may be active for the current process.