An open API service indexing awesome lists of open source software.

https://github.com/inaka/zipper

Generic Zipper implementation in Erlang
https://github.com/inaka/zipper

erlang hacktoberfest zipper

Last synced: 6 months ago
JSON representation

Generic Zipper implementation in Erlang

Awesome Lists containing this project

README

          

# 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)