# Zipper [![GitHub Actions CI](https://github.com/inaka/zipper/workflows/build/badge.svg)](https://github.com/inaka/zipper)
Generic zipper implementation in Erlang.
## Zippers: what are they good for?
Zippers let you traverse immutable data structures with ease and flexibility.
### Contact Us
If you find any **bugs** or have a **problem** while using this library, please
[open an issue](https://github.com/inaka/zipper/issues/new) in this repo (or a pull request :)).
And you can check all of our open-source projects at [inaka.github.io](https://inaka.github.io)
## Usage
For a map tree structure like the following:
```erlang
Root = #{type => planet,
attrs => #{name => "Earth"},
children => [
#{type => continent,
attrs => #{name => "America"},
children => [
#{type => country,
attrs => #{name => "Argentina"},
children => []},
#{type => country,
attrs => #{name => "Brasil"},
children => []}
]
},
#{type => continent,
attrs => #{name => "Europe"},
children => [
#{type => country,
attrs => #{name => "Sweden"},
children => []},
#{type => country,
attrs => #{name => "England"},
children => []}
]
}
]
},
```
You can build a zipper by providing three simple functions:
- `IsBranchFun`: takes a node and returns `true` if it is a branch node or
`false` otherwise.
- `ChildrenFun`: takes a node and returns a list of its children.
- `MakeNodeFun`: takes a node and a list of children and returns a new node
containing the supplied list as children.
This is an example of how you would define a zipper and then use it to traverse
the map tree structure above:
```erlang
%% Create the zipper
IsBranchFun = fun
(#{children := [_ | _]}) -> true;
(_) -> false
end,
ChildrenFun = fun(Node) -> maps:get(children, Node) end,
MakeNodeFun = fun(Node, Children) -> Node#{children => Children} end,
Zipper = zipper:new(fun is_map/1, ChildrenFun, MakeNodefun, Root),
%% Traverse the zipper with next
Zipper1 = zipper:next(Zipper),
Zipper2 = zipper:next(Zipper),
%% Get the current zipper node
Argentina = zipper:node(Zipper2).
io:format("~p", [Argentina]),
%%= #{type => country,
%%= attrs => #{name => "Argentina"},
%%= children => []}
%% Go up and get the node
Zipper3 = zipper:up(Zipper2).
America = zipper:node(Zipper2).
io:format("~p", [America]),
%%= #{type => country,
%%= attrs => #{name => "America"},
%%= children => [#{...}, #{...}]}
```
## Tests
Circular dependency in test environment ([Katana Test](https://github.com/inaka/katana-test) ->
[Elvis Core](https://github.com/inaka/elvis_core) -> [Zipper](https://github.com/inaka/zipper)) is
fixed by including Zipper as a dep in the test profile in `rebar.config`
```erlang
...
{profiles, [
{test, [
{deps, [
%% The tag will be replaced by the rebar.config.script
{zipper, {git, "https://github.com/inaka/zipper.git", {tag, "irrelevant"}}},
...
]}
]}
]}.
...
```
but then, we still replace the tag with the current branch. This is done in `rebar.config.script`.
Therefore, it's really important to have the branch updated and pushed to github before running the
tests with `rebar3 ct`.
## References
- [The Zipper, GERARD HUET](https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf)
- [clojure.zip](https://clojure.github.io/clojure/clojure.zip-api.html#clojure.zip/zipper)