defmodule Runbox.Utils.Traversal do
@moduledoc group: :utilities
@doc """
Performs a depth-first pre-order traversal on the given `value`.
Transforms each subterm via the given `fun`, before descending into its descendants.
## Example
iex> Traversal.prewalk(
...> [1, 2, :pi, 4, %{5 => [6, 7]}],
...> fn
...> x when is_integer(x) -> Integer.to_string(x)
...> x when is_list(x) -> Enum.reverse(x)
...> x -> x
...> end
...> )
[%{"5" => ["7", "6"]}, "4", :pi, "2", "1"]
"""
def prewalk(value, fun) do
value
|> fun.()
|> prewalk_children(fun)
end
defp prewalk_children(list, fun) when is_list(list) do
Enum.map(list, &prewalk(&1, fun))
end
defp prewalk_children(tuple, fun) when is_tuple(tuple) do
tuple
|> Tuple.to_list()
|> Enum.map(&prewalk(&1, fun))
|> List.to_tuple()
end
defp prewalk_children(%module{} = struct, fun) do
struct
|> Map.from_struct()
|> Map.new(fn {key, value} ->
{prewalk(key, fun), prewalk(value, fun)}
end)
|> Map.put(:__struct__, module)
end
defp prewalk_children(map, fun) when is_map(map) do
Map.new(map, fn {key, value} ->
{prewalk(key, fun), prewalk(value, fun)}
end)
end
defp prewalk_children(scalar, _fun) do
scalar
end
end