JSV.Helpers.Traverse (jsv v0.5.0)

View Source

Helper module to read and write to generic Elixir data structures.

Summary

Functions

Updates a data structure in depth-first, post-order traversal.

Updates a JSON-compatible data structure in depth-first, post-order traversal while carrying an accumulator.

Types

traverse_callback_elem()

@type traverse_callback_elem() ::
  {:key | term()} | {:val, term()} | {:struct, struct(), traverse_struct_cont()}

traverse_struct_cont()

@type traverse_struct_cont() :: (map(), term() -> {map(), term()})

Functions

postwalk(data, fun)

@spec postwalk(data, (traverse_callback_elem() -> data)) :: data when data: term()

Updates a data structure in depth-first, post-order traversal.

Operates like postwak/3 but without an accumulator. Handling continuations for structs require to handle the accumulator, whose value MUST be nil.

postwalk(data, accin, fun)

@spec postwalk(data, acc, (traverse_callback_elem(), acc -> {data, acc})) ::
  {data, acc}
when data: term(), acc: term()

Updates a JSON-compatible data structure in depth-first, post-order traversal while carrying an accumulator.

The callback must return a {new_value, new_acc} tuple.

Nested data structures are given to the callback before their wrappers, and when the wrappers are called, their children are already updated.

JSON-compatible only means that there are restrictions on map keys and struct values:

  • The callback function will be called for any key but will not traverse the keys. For instance, with data such as %{{x, y} => "some city"}, the tuple used as key will given called as-is but the callback will not be called for individual tuple elements.

  • Structs will be passed as {:struct, value, continuation}. The truct keys and values will NOT have been traversed yet. To operate on the struct keys you MUST call it manually. To respect the post-order of traversal, it SHOULD be called before further transformation of the struct:

    Traverse.postwalk(%MyStruct, [], fn
      {:struct, my_struct, cont}, acc ->
        {map, acc} = cont.(Map.from_struct(my_struct), acc)
        {struct!(MyStruct, do_something_with_map(map)), acc}
      {:val, ...} -> ...
    end)

    The continuation only accepts raw maps.

  • General data is accepted: tuples, pid, refs, etc. *