traverse v0.1.2 Traverse

Summary

Functions

filter allows to filter arbitrary substructures according to a filter function

map preserves structure, that is lists remain lists, tuples remain tuples and maps remain maps with the same keys, unless the transformation returns Traverse.Ignore (c.f. map1 if you want to transform key value pairs in maps)

mapall like map perserves the structure of the datastructure passed in

Traverse is a toolset to walk arbitrary Elixir Datastructures

Types

t_simple_filter_fn :: (any -> boolean)
t_simple_mapper_fn :: (any -> any)
t_simple_walker_fn :: (any, any -> any)
t_traceable_fn ::
  (any -> any) |
  (any, any -> any) |
  (any, any, any -> any)

Functions

filter(ds, filter_fn)

Specs

filter(any, t_simple_filter_fn) :: any

filter allows to filter arbitrary substructures according to a filter function.

The filter function does not need to be completely defined, undefined values are mapped to false. In other words we need to define the filter functions only for structures and values we want to keep.

iex> number_arrays = fn x when is_number(x) -> true
  ...>                    l when is_list(l)   -> true end
  ...> Traverse.filter([:a, {1, 2}, 3, [4, :b]], number_arrays)
  [3, [4]]

The same result can be achieved with mapall and Traverse.Ignore if that suits your style better:

iex> not_number_arrays = fn x when is_number(x) or is_list(x) -> x
  ...>                    _   -> Traverse.Ignore end
  ...> Traverse.mapall([:a, {1, 2}, 3, [4, :b]], not_number_arrays)
  [3, [4]]
map(ds, mapper_fn)

Specs

map(any, t_simple_mapper_fn) :: any

map preserves structure, that is lists remain lists, tuples remain tuples and maps remain maps with the same keys, unless the transformation returns Traverse.Ignore (c.f. map1 if you want to transform key value pairs in maps)

In order to avoid putting unnecessary burden on the transformer function it can only be partially defined, and it will be completed with the identity function for undefined parameters. Here is an example.

iex> Traverse.map([:a, 1, {:b, 2}], fn x when is_number(x) -> x + 1 end)
  [:a, 2, {:b, 3}]

The transformer function can also return the special value Traverse.Ignore, which will remove the value from the result, and in case of a map it will remove the key, value pair.

iex> require Integer
  ...> no_odds = fn x when Integer.is_even(x) -> x * 2
  ...>              _                 -> Traverse.Ignore end
  ...> Traverse.map([1, %{a: 1, b: 2}, {3, 4}], no_odds)
  [%{b: 4}, {8}]

The more general way to achieve this is to use filter_map, which however is less efficent as the filter function is also called on inner nodes.

mapall(ds, mapper_fn, options \\ [])

Specs

mapall(any, t_simple_mapper_fn, Keyword.t) :: any

mapall like map perserves the structure of the datastructure passed in.

However it also calls the transformer function for inner nodes, which allows us to perform mappings on substructures.

Again the transformer function can be partially defined and is completed by the identity function.

And, also again, the special return value Traverse.Ignore can be used to ignore values or substructures.

Here is a simple example that eliminates empty sublists

iex> [1, [[]], 2, [3, []]] …> |> Traverse.mapall(fn [] -> Traverse.Ignore end) [1, [], 2, [3]]

This example shows that mapall applies a prewalk strategy by default, we can change this by providing the option post: true.

iex> [1, [[]], 2, [3, []]] …> |> Traverse.mapall(fn [] -> Traverse.Ignore end, post: true) [1, 2, [3]]

Now, by applying the transformation after having transformed the substructure, empty lists of empty lists go away too.

walk(ds, initial_acc, walker_fn)

Specs

walk(any, any, t_simple_walker_fn) :: any

Traverse is a toolset to walk arbitrary Elixir Datastructures.

walk visits all substructures down to atomic elements.

iex>    ds = [:a, {:b, 1, 2}, [:c, 3, 4, 5]]
...>    collector =  fn ele, acc when is_atom(ele) or is_number(ele) -> [ele|acc]
...>                    _,   acc                    -> acc       end
...>    Traverse.walk(ds, [], collector)
[5, 4, 3, :c, 2, 1, :b, :a]

One can return the accumulator boxed in a %Cut{} struct to avoid traversal of the subtree.

iex>   ds = [add: [1, 2], ignore: [3, 4]]
...>   collector = fn {:ignore, _}, acc        -> %Traverse.Cut{acc: acc}
...>                  n, acc when is_number(n) -> [n|acc]
...>                  _, acc                   -> acc end
...>   Traverse.walk(ds, [], collector)
[2, 1]