JSV.Helpers.Traverse (jsv v0.10.0)

View Source

Helpers to work with nested generic 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.

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

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

Types

postwalk_cont()

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

postwalk_item()

@type postwalk_item() ::
  {:key, term()} | {:val, term()} | {:struct, struct(), postwalk_cont()}

prewalk_item()

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

Functions

postwalk(data, fun)

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

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

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

postwalk(data, acc, fun)

@spec postwalk(data, acc, (postwalk_item(), 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 be passed as-is but the callback will not be called for individual tuple elements.

  • Structs will be passed as {:struct, value, continuation}. The struct 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. *

prewalk(data, fun)

@spec prewalk(data, (prewalk_item() -> data)) :: data when data: term()

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

Operates like prewalk/3 but without an accumulator.

prewalk(data, acc, fun)

@spec prewalk(data, acc, (prewalk_item(), acc -> {data, acc})) :: {data, acc}
when data: term(), acc: term()

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

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

Nested data structures are given iterated after the parent data has been given to the function. So it is possible to accept a container (map, list, tuple) and return another one from the callback before the children are iterated.

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 be passed as-is but the callback will not be called for individual tuple elements.
  • Structs are passed as a {:struct, struct} tuple.
  • General data is accepted: tuples, pid, refs, etc. *