Ecosyste.ms: Awesome

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

Awesome Lists | Featured Topics | Projects

https://github.com/turbomack/lazy-tree-with-zipper

Lazy rose tree (multiway tree) with zipper. In Elm
https://github.com/turbomack/lazy-tree-with-zipper

data-structure elm elm-lang multiway-tree rose-tree zipper

Last synced: 2 months ago
JSON representation

Lazy rose tree (multiway tree) with zipper. In Elm

Awesome Lists containing this project

README

        

# Lazy Tree with Zipper

[![Build Status](https://travis-ci.org/turboMaCk/lazy-tree-with-zipper.svg?branch=master)](https://travis-ci.org/turboMaCk/lazy-tree-with-zipper)

This is pure Elm [rose tree](https://en.wikipedia.org/wiki/Rose_tree)
with [zipper](https://en.wikipedia.org/wiki/Zipper_%28data_structure%29) implementation.
In the context of Elm, this data structure is mostly useful for building hierarchical interfaces
like menus, data browsers or filters.

Main features of this library are things like easy building tree structure from flat Lists
with very good performance characteristics, powerful and extensible zipper and feature-rich API.

# Performance

`Tree` is using custom List like implementation (`LList`) to enable lazy level after level evaluation
of tree. In fact `LList` is just a function that constructs plain old `List`. This approach is the main performance optimization used in this library.

There is another library implementing same idea in slightly different way [tomjkidd/elm-multiway-tree-zipper](https://github.com/tomjkidd/elm-multiway-tree-zipper).
The main difference is that `elm-multiway-tree-zipper` implementation is strict so the whole Tree is immediately evaluated.
Implementation provided by this package is optimized for situations in which it isn't necessary to construct the whole
structure immediately. In situations where Tree is expanded level by level this implementation yields
much better performance than strict implementation, especially for large trees.
You can find basic comparison in [performance](https://github.com/turboMaCk/lazy-tree-with-zipper/blob/master/performance).

Feedback and contributions to both code and documentation are very welcome.

# Usage

To install this package run:

```
$ elm install turboMaCk/lazy-tree-with-zipper
```

This is an example application that renders levels of items as nested tree
with toggling between open and closed state in every level:

```elm
module Main exposing (main)

import Browser
import Html exposing (Html)
import Html.Events as Events
import Lazy.Tree as Tree exposing (Tree(..))
import Lazy.Tree.Zipper as Zipper exposing (Zipper)

main : Program () Model Msg
main =
Browser.sandbox
{ init = init
, update = update
, view = view
}

-- Model

type alias Item =
{ id : Int
, name : String
, parent : Maybe Int
}

items : List Item
items =
[ { id = 1, name = "Foo", parent = Nothing }
, { id = 2, name = "Bar", parent = Nothing }
, { id = 3, name = "Baz", parent = Nothing }
, { id = 4, name = "Foobar", parent = Just 1 }
, { id = 5, name = "Bar child", parent = Just 2 }
, { id = 6, name = "Foobar child", parent = Just 4 }
]

{-| Zipper of pair where first value means `isOpen` and second contain Item details.
-}
type alias Model =
Zipper ( Bool, Item )

init : Model
init =
let
root =
{ id = -1, name = "root", parent = Nothing }
in
List.map (\b -> ( False, b )) items
|> Tree.fromList (\p ( _, i ) -> Maybe.map (.id << Tuple.second) p == i.parent)
|> Tree ( False, root )
|> Zipper.fromTree

-- Update

type Msg
= Toggle (Zipper ( Bool, Item ))

update : Msg -> Model -> Model
update (Toggle zipper) _ =
Zipper.updateItem (\( s, i ) -> ( not s, i )) zipper

-- View

view : Model -> Html Msg
view zipper =
Html.ul [] [ viewLevel (Zipper.root zipper) ]

viewLevel : Zipper ( Bool, Item ) -> Html Msg
viewLevel zipper =
let
( isOpen, item ) =
Zipper.current zipper
in
Html.li []
[ Html.a [ Events.onClick <| Toggle zipper ]
[ if not (Zipper.isEmpty zipper) then
Html.span []
[ if isOpen then
Html.text "- "

else
Html.text "+ "
]

else
Html.text ""
, Html.text item.name
]
, Html.ul [] <|
if isOpen then
Zipper.openAll zipper
|> List.map viewLevel

else
[]
]
```

# Background

I've spent about a year experimenting with different ideas of Rose Tree implementation
optimized for needs of building UIs for recursive data. The biggest problem turned out to be performance.
Usually, data for web applications are coming from a server which uses SQL database as storage.
API usually then renders flat JSON or any other data format which uses references to describe recursive relationships.
Therefore one of the main features that are needed is an efficient and easy way to build tree from a list of data.
This usually results in quadratic complexity. Since one item might be a child of multiple other things
there has to be at least one iteration over the whole list of data. Also by definition using such data
for building rose tree might result in infinitely deep resulting tree.

Those are the things I've experimented with over time:

- Strict based (`Tree a (List a)`) - not that great performance but OK until you hit too much recursion.
- Lazy List based implementation (`Tree a (LazyList a)`) - runs into too much recursion even on simpler data.
- Continuation Passing / CPS - very slow (hitting scripts runs for too long) - might have been an issue with a particular algorithm.
- Lazy List construction - this is what this package is using - very best performance.

# License

This package is released under BSD-3-Clause license. See [LICENSE](LICENSE) file for more info.