phst_transform v1.0.2 PhStTransform protocol
The PhStTransform
protocol will convert any Elixir data structure
using a given transform into a new data structure.
The transform/3
function takes the data structure and
a map of transformation functions and a depth list. It
then does a depth-first recursion through the structure,
applying the tranformation functions for all
data types found in the data structure.
The transform map has data types as keys and anonymous functions as values. The anonymous functions have the data item and optionally a recursion depth list as inputs and can return anything. These maps of transform functions are refered to as potions.
The transmogrify/3
function is similar except that it allows
the functions to modify the potion map as the tranform is
in progress and it returns a tuple consisting of the
transformed data and potion.
Example: Convert all atoms to strings
atom_to_string_potion = %{ Atom => fn(atom) -> Atom.to_string(atom) end }
PhStTransform.transform(data, atom_to_string_potion)
The potion map should have Elixir Data types as keys and anonymous functions
of either fn(x)
or fn(x, depth)
arity. You can supply nearly any kind of map
as an argument however, since the PhStTransform.Potion.brew
function will strip
out any invalid values. The valid keys are all of the standard Protocol types:
[Atom, Integer, Float, BitString, Regexp, PID, Function, Reference, Port, Tuple, List, Map]
plus Keyword
and the name of any defined Structs (e.g. Range
)
There is also the special type Any
, this is the default function applied
when there is no function for the type listed in the potion. By default
this is set to the identity function fn(x, _d) -> x end
, but can be overridden
in the initial map.
The depth argument should always be left at the default value when using this protocol. For the anonymous functions in the potion map, they can use the depth list to know which kind of data structure contains the current data type.
Example: Capitalize all strings in the UserName struct, normalize all other strings.
user_potion = %{ BitString => fn(str, depth) ->
if(List.first(depth) == UserName), do: String.capitalize(str), else: String.downcase(str)) end}
PhStTransform.transform(data, user_potion)
Example: Parse a list of strings input from a CSV file, into a list of maps.
csv_potion = %{ BitString => fn(str, potion) ->
keys = String.split(str, ",")
new_potion = Map.put(potion, BitString, fn(str, potion) ->
{ String.split(str,",")
|> Enum.zip(keys)
|> Enum.reduce( %{}, fn(tuple, map) ->
{v, k} = tuple
Map.put(map,k,v) end),
potion }
end )
{keys, new_potion}
end }
csv_strings = File.stream!("file.csv") |> Enum.into([])
{[keys | maps ], new_potion } = PhStTranform.transmogrify(csv_strings, csv_potion)
Summary
Functions
uses the given function_map to transform any Elixir data structure
Works similarly to transform, but returns a tuple consisting of {result, potion} allowing self modifying potions
Types
t :: term
Functions
uses the given function_map to transform any Elixir data structure.
function_map
should contain keys that correspond to the data types
to be transformed. Each key must map to a function that takes that data
type and optionally the depth list as arguments.
depth
should always be left at the default value since it is meant for
internal recursion.
Examples
iex> atom_to_string_potion = %{ Atom => fn(atom) -> Atom.to_string(atom) end }
iex> PhStTransform.transform([[:a], :b, {:c, :e}], atom_to_string_potion)
[["a"], "b", {"c", "e"}]
Works similarly to transform, but returns a tuple consisting of {result, potion} allowing self modifying potions.
Examples
iex> atom_first = %{ Atom => fn(atom, potion) ->
old = atom
{ atom, Map.put(potion, Atom, fn(atom, potion) ->
{old, potion} end )} end }
iex> PhStTransform.transmorgrify([:a, :b, :c, :d], atom_first)
{[:a, :a, :a, :a], %{Atom => #Function<12.54118792/2 in :erl_eval.expr/5>} }