plymio_vekil v0.1.0 Plymio.Vekil.Form View Source

This module implements the Plymio.Vekil protocol using a Map where the proxies (keys) are atoms and the foroms (values) hold quoted forms.

The default when creating a form vekil is to create Plymio.Vekil.Forom.Form forom but any vekil can hold any forom.

See Plymio.Vekil for the definitions of the protocol functions.

Module State

See Plymio.Vekil for the common fields.

The module’s state is held in a struct with the following field(s):

FieldAliasesPurpose
:dict:dholds the map of proxies v forom
:forom_normalisesee field description
:proxy_normalisesee field description

Module State Field: :forom_normalise

The :forom_normalise field holds an arity 1 or 2 function.

If it is arity 2, it is passed the same arguments to the vekil’s forom_normalise/2 function and must return {:ok, {forom, vekil}}.

If it is arity 1, just the second argument from the call to the vekil’s forom_normalise/2 function is passed and must return {:ok, forom}.

The default for this vekil is Plymio.Vekil.Forom.Form.normalise/1.

Module State Field: :proxy_normalise

The :proxy_normalise field holds an arity 1 function that is usually passed the second argumnet from the call to the vekil’s proxy_normalise/2 function.

The default for this vekil is Plymio.Fontais.Utility.validate_key/1.

Test Environment

See also notes in Plymio.Vekil.

The vekil created in the example below of new/1 is returned by vekil_helper_form_vekil_example1/0.

iex> {:ok, vekil} = new()
...> dict = [
...>    x_add_1: quote(do: x = x + 1),
...>    x_mult_x: quote(do: x = x * x),
...>    x_sub_1: quote(do: x = x - 1),
...>    x_funs: [:x_add_1, :x_mul_x, :x_sub_1],
...>    x_loop: [:x_add_1, :x_loop, :x_sub_1]
...> ]
...> {:ok, vekil} = vekil |> update(dict: dict)
...> match?(%VEKILFORM{}, vekil)
true

Link to this section Summary

Link to this section Types

Link to this section Functions

Link to this function forom_normalise(vekil, value \\ []) View Source
forom_normalise(any(), any()) :: {:ok, {forom(), t()}} | {:error, error()}

See Plymio.Vekil.forom_normalise/2

The default action is to create a Plymio.Vekil.Forom.Form forom,

Examples

Here the value being normalised is a simple statement:

iex> %VEKILFORM{} = vekil = vekil_helper_form_vekil_example1()
...> value = quote(do: x = x + 1)
...> {:ok, {forom, %VEKILFORM{}}} = vekil |> VEKILPROT.forom_normalise(value)
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 2])
{3, ["x = x + 1"]}

An existing forom is returned unchanged.

iex> %VEKILFORM{} = vekil = vekil_helper_form_vekil_example1()
...> {:ok, %Plymio.Vekil.Forom.Form{} = forom} = quote(do: x = x + 1)
...>   |> Plymio.Vekil.Forom.Form.normalise
...> {:ok, {forom, %VEKILFORM{}}} = vekil |> VEKILPROT.forom_normalise(forom)
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 2])
{3, ["x = x + 1"]}

In this example the value is :x_funs which means it is a proxy and is normalised into a proxy forom. When the proxy forom is realised, the original atom (i.e. :x_funs) is used in a proxy_fetch/2 and the forom from the fetch realised.

iex> %VEKILFORM{} = vekil = vekil_helper_form_vekil_example1()
...> value = :x_funs
...> {:ok, {%Plymio.Vekil.Forom.Proxy{} = forom, %VEKILFORM{}}} = vekil
...>    |> VEKILPROT.forom_normalise(value)
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}
Link to this function has_proxy?(vekil, proxy) View Source
has_proxy?(t(), any()) :: boolean()

See Plymio.Vekil.has_proxy?/2

Note: the proxy is not normalised in any way.

Examples

Here a known proxy is tested for:

iex> vekil_helper_form_vekil_example1()
...> |> VEKILPROT.has_proxy?(:x_sub_1)
true

An unknown proxy returns false

iex> vekil_helper_form_vekil_example1()
...> |> VEKILPROT.has_proxy?(:not_a_proxy)
false

iex> vekil_helper_form_vekil_example1()
...> |> VEKILPROT.has_proxy?(%{a: 1})
false
Link to this function new(opts \\ []) View Source
new(any()) :: {:ok, t()} | {:error, error()}

new/1 takes an optional opts and creates a new form vekil returning {:ok, vekil}.

Examples

iex> {:ok, vekil} = new()
...> match?(%VEKILFORM{}, vekil)
true

Plymio.Vekil.Utility.vekil?/1 returns true if the value implements Plymio.Vekil

iex> {:ok, vekil} = new()
...> vekil |> Plymio.Vekil.Utility.vekil?
true

The vekil dictionary can be supplied as a Map or Keyword. It will be validated to ensure all the proxies are atoms and all the forom are valid forms.

iex> {:ok, vekil} = [dict: [
...>    x_add_1: quote(do: x = x + 1),
...>    x_mul_x: quote(do: x = x * x),
...>    x_sub_1: quote(do: x = x - 1),
...>    x_funs: [:x_add_1, :x_mul_x, :x_sub_1]
...> ]] |> new()
...> match?(%VEKILFORM{}, vekil)
true

In the above example the :x_funs proxy in the vekil dictionary was given a list of 3 atoms: [:x_add_1, :x_mul_x, :x_sub_1]. Each of the atoms is treated as a reference to another proxy in the vekil and are normalised into instances of Plymio.Vekil.Forom.Proxy (a proxy forom).

List of atoms provide a simple way of defining a composite forom out of constituent forom.

See the e.g. proxy_fetch/2 examples for how this works in practice.

Link to this function new!(opts \\ []) View Source
new!(any()) :: t() | no_return()

new!/1 calls new/1 and, if the result is {:ok, instance} returns the instance.

Link to this function proxy_delete(vekil, proxies) View Source
proxy_delete(t(), any()) :: {:ok, t()} | {:error, error()}

See Plymio.Vekil.proxy_delete/2

Note proxies are normalised.

Examples

Here a known proxy is deleted and then fetched, causing an error:

iex> {:ok, %VEKILFORM{} = vekil} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_delete(:x_sub_1)
...> {:error, error} = vekil |> VEKILPROT.proxy_fetch([:x_add_1, :x_sub_1])
...> error |> Exception.message
"proxy invalid, got: :x_sub_1"

This example deletes :x_mul_x and but provides quote(do: x = x * x * x) as the default in the following get:

iex> {:ok, %VEKILFORM{} = vekil} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_delete(:x_mul_x)
...> {:ok, {forom, %VEKILFORM{}}} = vekil
...> |> VEKILPROT.proxy_get([:x_add_1, :x_mul_x, :x_sub_1], quote(do: x = x * x * x))
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{511, ["x = x + 1", "x = x * x * x", "x = x - 1"]}

Deleting unknown proxies does not cause an error:

iex> {:ok, %VEKILFORM{} = vekil} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_delete([:x_sub_1, :not_a_proxy, :x_mul_x])
...> vekil |> Plymio.Vekil.Utility.vekil?
true
Link to this function proxy_fetch(vekil, proxies) View Source
proxy_fetch(t(), any()) :: {:ok, {product(), t()}} | {:error, error()}

See Plymio.Vekil.proxy_fetch/2.

Examples

A single proxy is fetched:

iex> {:ok, {forom, %VEKILFORM{}}} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_fetch(:x_add_1)
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{8, ["x = x + 1"]}

Two proxies are fetched:

iex> {:ok, {forom, %VEKILFORM{}}} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_fetch([:x_mul_x, :x_add_1])
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{50, ["x = x * x", "x = x + 1"]}

proxies is nil / empty. Note the use and override of :realise_default.

iex> {:ok, {forom, %VEKILFORM{}}} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_fetch(nil)
...> {:ok, {value, _}} = forom |> FOROMPROT.realise
...> value |> Plymio.Fontais.Guard.is_value_unset
true

iex> {:ok, {forom, %VEKILFORM{}}} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_fetch([])
...> {:ok, {forms, _}} = forom
...> |> FOROMPROT.realise(realise_default: quote(do: x = x + 35))
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{42, ["x = x + 35"]}

One or more proxies not found

iex> {:error, error} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_fetch(:not_a_proxy)
...> error |> Exception.message
"proxy invalid, got: :not_a_proxy"

iex> {:error, error} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_fetch([:missing_proxy, :x_sub_1, :not_a_proxy])
...> error |> Exception.message
"proxies invalid, got: [:missing_proxy, :not_a_proxy]"

Proxy loops are caught:

iex> {:ok, %VEKILFORM{} = vekil} = [dict: [
...>    x_add_1: quote(do: x = x + 1),
...>    x_mul_x: quote(do: x = x * x),
...>    x_sub_1: quote(do: x = x - 1),
...>    x_loopa: [:x_add_1, :x_loopb, :x_sub_1],
...>    x_loopb: [:x_add_1, :x_sub_1, :x_loopc],
...>    x_loopc: [:x_loopa, :x_add_1, :x_sub_1],
...> ]] |> VEKILFORM.new()
...> {:ok, {forom, %VEKILFORM{}}} = vekil |> VEKILPROT.proxy_fetch(:x_loopa)
...> {:error, error} = forom |> FOROMPROT.realise
...> error |> Exception.message
"proxy seen before, got: :x_loopa"

In this example the proxy is :x_funs which was defined in the vekil dictionary as a list of 3 atoms: [:x_add_1, :x_mul_x, :x_sub_1].

Each of the atoms is treated as a reference to another proxy in the vekil and are normalised into instances of Plymio.Vekil.Forom.Proxy (a proxy forom). Since the fetch must return a forom, the 3 proxy foroms are normalised to a Plymio.Vekil.Forom.List for the result.

When a proxy forom is realised, the original atom proxy (e.g. :x_sub_1) is used in a proxy_fetch/2 and the forom returned by the fetch is realised.

iex> {:ok, {forom, %VEKILFORM{}}} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_fetch(:x_funs)
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}
Link to this function proxy_get(vekil, proxies) View Source
proxy_get(t(), proxies()) :: {:ok, {product(), t()}} | {:error, error()}

See Plymio.Vekil.proxy_get/2

Examples

A single known, proxy is requested with no default

iex> {:ok, {forom, %VEKILFORM{}}} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_get(:x_add_1)
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{8, ["x = x + 1"]}

Two known proxies are requested:

iex> {:ok, {forom, %VEKILFORM{}}} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_get([:x_mul_x, :x_add_1])
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{50, ["x = x * x", "x = x + 1"]}

A single unknown, proxy is requested with no default. Note the use of :realise_default to provide a backstop default.

iex> {:ok, {forom, %VEKILFORM{}}} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_get(:not_a_proxy)
...> {:ok, {forms, _}} = forom
...> |> FOROMPROT.realise(realise_default: quote(do: x = x + 35))
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{42, ["x = x + 35"]}
Link to this function proxy_get(vekil, proxies, default) View Source
proxy_get(t(), any(), any()) ::
  {:ok, {product(), t()}} | {:error, error()}

See Plymio.Vekil.proxy_get/3

Examples

A single unknown proxy is requested with a default:

iex> {:ok, {forom, %VEKILFORM{}}} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_get(:not_a_proxy, quote(do: x = x *x * x))
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{343, ["x = x * x * x"]}

A mix of known and unknown proxies, together with a default:

iex> {:ok, {forom, %VEKILFORM{}}} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_get([:missing_proxy, :x_sub_1, :not_a_proxy], quote(do: x = x * x * x))
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 2])
{343, ["x = x * x * x", "x = x - 1", "x = x * x * x"]}

The default is not a valid form(s)

iex> {:error, error} = vekil_helper_form_vekil_example1()
...> |> VEKILPROT.proxy_get(:not_a_proxy, %{a: 1})
...> error |> Exception.message
"default invalid, got: %{a: 1}"
Link to this function proxy_put(vekil, tuples) View Source
proxy_put(t(), any()) :: {:ok, t()} | {:error, error()}

See Plymio.Vekil.proxy_put/2

Examples

A list of {proxy,form} tuples can be given. Since a form vekil’s proxy is an atom, Keyword syntax can be used here:

iex> {:ok, %VEKILFORM{} = vekil} = VEKILFORM.new()
...> {:ok, %VEKILFORM{} = vekil} = vekil |> VEKILPROT.proxy_put(
...>    x_add_1: quote(do: x = x + 1),
...>    x_mul_x: quote(do: x = x * x),
...>    x_sub_1: quote(do: x = x - 1),
...>    x_funs: [:x_add_1, :x_mul_x, :x_sub_1])
...> {:ok, {forom, %VEKILFORM{}}} = vekil |> VEKILPROT.proxy_fetch(:x_funs)
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}
Link to this function proxy_put(vekil, proxy, forom) View Source
proxy_put(t(), any(), any()) :: {:ok, t()} | {:error, error()}

See Plymio.Vekil.proxy_put/3

Examples

This example puts a proxy into an empty vekil and then fetches it.

iex> {:ok, %VEKILFORM{} = vekil} = VEKILFORM.new()
...> {:ok, %VEKILFORM{} = vekil} = vekil
...>    |> VEKILPROT.proxy_put(:x_add_1, quote(do: x = x + 1))
...> {:ok, {forom, %VEKILFORM{}}} = vekil |> VEKILPROT.proxy_fetch(:x_add_1)
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{8, ["x = x + 1"]}

The proxy can have/put multiple forms:

iex> {:ok, %VEKILFORM{} = vekil} = VEKILFORM.new()
...> {:ok, %VEKILFORM{} = vekil} = vekil |> VEKILPROT.proxy_put(:x_add_mul_sub,
...>  [quote(do: x = x + 1), quote(do: x = x * x), quote(do: x = x - 1)])
...> {:ok, {forom, %VEKILFORM{}}} = vekil |> VEKILPROT.proxy_fetch(:x_add_mul_sub)
...> {:ok, {forms, _}} = forom |> FOROMPROT.realise
...> forms |> harnais_helper_test_forms!(binding: [x: 7])
{63, ["x = x + 1", "x = x * x", "x = x - 1"]}
Link to this function update(t, opts \\ []) View Source
update(t(), opts()) :: {:ok, t()} | {:error, error()}

update/2 takes a vekil and opts and update the field(s) in the vekil from the {field,value} typles in the opts.

Examples

iex> {:ok, vekil} = new()
...> dict = [
...>    x_add_1: quote(do: x = x + 1),
...>    x_mul_x: quote(do: x = x * x),
...>    x_sub_1: quote(do: x = x - 1),
...>    x_funs: [:x_add_1, :x_mul_x, :x_sub_1]
...> ]
...> {:ok, vekil} = vekil |> update(dict: dict)
...> match?(%VEKILFORM{}, vekil)
true
Link to this function update!(t, opts \\ []) View Source
update!(t(), any()) :: t() | no_return()

update!/2 calls update/2 and, if the result is {:ok, instance} returns the instance.