Letex v0.1.0 Letex View Source
Lisp-esque Let to support easy stateful lexical closures in Elixir
Link to this section Summary
Functions
Takes a parent agent pid, and a child agent pid. Adds the child agent pid to the parents list of child agents. This is done so that when we free a parent, we can walk down the tree and free all of its children as well
When using def to create a named function, the var used to store the current leaf of the tree of agents (or nil to represent the root) is not initialized to nil as it needs to be. This macro wraps def with a call to initialize this var to nil beforehand so that calls to let will function correctly within the def body
Function to perform the recursive freeing of agents. It recursively calls do_free for each child in the current Agents list of children, removes the current agent from it's parents list of children, and then finally stops the current Agent
This function is used to actually retrieve the requested value from the appropriate agent. It fetches values in the different agents from bottom to top in the hierarchy of lets. Returns an error if the value is not found anywhere in the hierarchy
This function is used to actually set the variable to the new value in the appropriate agent. It searches for the variable to be present in the agents from the bottom to top of the hierarchy of the surrounding lets
This function is used to actually perform the updating action on the correct value in the appropriate agent
When this macro is called, it sends a stop signal to all children of the nearest layered agent, followed by sending a stop signal to the nearest layered agent
Accepts a symbol of a let-bound variable. Returns the value bound to that variable. If a let binds a variable that is already bound by an outer let, the inner binding will shadow the outer binding
Accepts a keyword-list and a do block. Initializes all the keys in the list to their associated values. Calls to get(key) within the let will retrieve the value bound to key. Calls to set(key, val) will update the state of key to val. Calls to update(key, arity_1_function) will update the state of key to the result of calling the function on the current state of the key
A variant of let which links the agents it spawns to the current process in order to make managing resource utilization easier. This means that the lexical context created by the let_link will not be able to out-live the calling process (e.g. if you create a counter in the current process, hand it off to some other process to use, and then the original process ends, subsequent calls to the counter from the other process will fail). However if the context does not need to out-live the calling process, this can make it much easier to avoid leaking agents
Variant of let which automatically wraps the return of the let to be a pair. The first element is the result of evaluating the body passed to the let, and the second is a 0-arity function which frees the agent (and all of its children) created by the let
Accepts a symbol of a let-bound variable, and a new value. Binds the variable to the new value. Returns the newly set value. If the symbol passed does not match a let-bound variable in scope, will return an error
Update the given let-bound variable by calling the given 1-arity function on it. Returns the result, or an error if the binding was not found in the current scope
Link to this section Functions
add_child(parent, child) View Source
Takes a parent agent pid, and a child agent pid. Adds the child agent pid to the parents list of child agents. This is done so that when we free a parent, we can walk down the tree and free all of its children as well.
defun(args, list) View Source (macro)
When using def to create a named function, the var used to store the current leaf of the tree of agents (or nil to represent the root) is not initialized to nil as it needs to be. This macro wraps def with a call to initialize this var to nil beforehand so that calls to let will function correctly within the def body.
Apart from allowing let forms within the body of a def, they behave identically to a normal def
Examples:
defun make_counter_maker(initial_iv) do
# Here we use let because we want this counter maker to live forever
let [iv: initial_iv] do
fn ->
# Here we use let because these counters need to out-live their calling processes for
# whatever reason. However, we want to be able to kill the agents eventually, so we
# return a pair of functions, the first of which calls the counter, and the second
# of which frees the agent wrapping the counter.
let [count: get(:iv)] do
{fn -> set(:iv, update(:count, &(&1 + 1))) end, fn -> free() end}
end
end
end
end
do_free(agent) View Source
Function to perform the recursive freeing of agents. It recursively calls do_free for each child in the current Agents list of children, removes the current agent from it's parents list of children, and then finally stops the current Agent.
do_get(pid, val) View Source
This function is used to actually retrieve the requested value from the appropriate agent. It fetches values in the different agents from bottom to top in the hierarchy of lets. Returns an error if the value is not found anywhere in the hierarchy.
do_set(pid, var, val) View Source
This function is used to actually set the variable to the new value in the appropriate agent. It searches for the variable to be present in the agents from the bottom to top of the hierarchy of the surrounding lets.
do_update(pid, var, func) View Source
This function is used to actually perform the updating action on the correct value in the appropriate agent.
free() View Source (macro)
When this macro is called, it sends a stop signal to all children of the nearest layered agent, followed by sending a stop signal to the nearest layered agent.
get(var) View Source (macro)
Accepts a symbol of a let-bound variable. Returns the value bound to that variable. If a let binds a variable that is already bound by an outer let, the inner binding will shadow the outer binding.
Examples:
iex> use Letex
iex> let [x: 5] do get(:x) end
5
iex> use Letex
iex> let [x: 5] do let [x: 10] do get(:x) end end
10
iex> use Letex
iex> let [x: 5] do let [x: 10] do get(:y) end end
{:error, "Failed to get value for binding: Binding not found"}
let(bindings, list) View Source (macro)
Accepts a keyword-list and a do block. Initializes all the keys in the list to their associated values. Calls to get(key) within the let will retrieve the value bound to key. Calls to set(key, val) will update the state of key to val. Calls to update(key, arity_1_function) will update the state of key to the result of calling the function on the current state of the key.
The unique feature of these lets is that they create a stateful execution context, allowing for the easy creation of stateful lexical closures.
This macro achieves this by spawning agents to hold the lexical scope in which the body executes. However, these agents are not linked to the calling process in order to allow for the lexical scope to out-live the calling process. This means this state must be manually freed, or it will live forever using memory. If you do not need the scope to out-live the calling process, then you should use let_link instead (It behaves the same as let except that the agents are created with start_link instead of start). Otherwise you can use the free macro within the body of your lets in order to manage this.
Examples:
iex> use Letex
iex> {counter, free} = let [count: 0] do
...> {fn -> update(:count, &(&1 + 1)) end, fn -> free() end}
...> end
iex> counter.()
1
iex> counter.()
2
iex> free.()
:ok
Inner bindings with the same variable name as an outer binding will "shadow" the outer binding, meaning all references within the inner let will resolve the variable to the inner version (which has its own state storage), where all references within the outer let, but outside of the inner one will use the outer version.
iex> use Letex
iex> let [x: 5] do {let [x: 10] do get(:x) end, get(:x)} end
{10, 5}
let_link(bindings, list) View Source (macro)
A variant of let which links the agents it spawns to the current process in order to make managing resource utilization easier. This means that the lexical context created by the let_link will not be able to out-live the calling process (e.g. if you create a counter in the current process, hand it off to some other process to use, and then the original process ends, subsequent calls to the counter from the other process will fail). However if the context does not need to out-live the calling process, this can make it much easier to avoid leaking agents.
Examples:
iex> use Letex
iex> counter = let_link [count: 0] do fn -> update(:count, &(&1 + 1)) end end
iex> counter.()
1
iex> counter.()
2
let_wrapped(bindings, list) View Source (macro)
Variant of let which automatically wraps the return of the let to be a pair. The first element is the result of evaluating the body passed to the let, and the second is a 0-arity function which frees the agent (and all of its children) created by the let.
Examples:
iex> use Letex
iex> {counter, free} = let_wrapped [count: 0] do
...> fn -> set(:count, get(:count) + 1) end
...> end
iex> counter.()
1
iex> counter.()
2
iex> free.()
:ok
set(var, val) View Source (macro)
Accepts a symbol of a let-bound variable, and a new value. Binds the variable to the new value. Returns the newly set value. If the symbol passed does not match a let-bound variable in scope, will return an error.
Examples:
iex> use Letex
iex> let [x: 5] do set(:x, 10) end
10
iex> use Letex
iex> let [x: 5] do set(:y, 10) end
{:error, "Unable to set binding: Binding not found"}
update(var, func) View Source (macro)
Update the given let-bound variable by calling the given 1-arity function on it. Returns the result, or an error if the binding was not found in the current scope.
Examples:
iex> use Letex
iex> let [x: 5] do update(:x, &(&1 + 1)) end
6
iex> use Letex
iex> let [x: 5] do update(:y, &(&1 + 1)) end
{:error, "Unable to update binding: Binding not found"}