A zipper is a way to navigate and change data structures. Suppose you have a tree and you go through the tree, choosing a branch. After 3 steps, you come across a sheet, and you can modify it. Now you want to return the changed tree, but you are 3 levels deep, how do you retreat? Suppose that every time you go down to the level of a tree, you leave a trail of crackers so that you can undo the step and restore the tree. In other words, instead of the whole tree, you have a tuple (tree, breadcrumbs).
type Tree state = Branch (List (Tree state)) | Leaf state -- Crumb contains 2 lists: -- leafs/branches to the left of your focus -- leafs/branches to the right of your focus type Crumb state = Crumb (List (Tree state)) (List (Tree state)) type alias Zipper state = (Tree state, List (Crumb state)) tree : Tree String tree = Branch [Leaf "foo",Leaf "bar",Branch [Leaf "baz",Branch [],Leaf "qux"]]
Each time you select a branch, you disagree with the parent node and all other branches that you did not select in the breadcrumbs. Then you select a new branch, so you do not agree with its parent mode and all new branches that you did not select in the breadcrumbs. Therefore, breadcrumbs contain all the necessary information to restore the previous step in the reverse order. Let the movement up and transition to the sheet by name in the tree be implemented:
import Graphics.Element exposing (show) break : (a -> Bool) -> List a -> (List a, List a) break p xs = case (List.head xs, List.tail xs) of (Nothing, Nothing) -> ([], []) (Just x, Just xs') -> if px then ([], xs) else let (ys,zs) = break p xs' in (x::ys,zs) treeInit : Tree state -> Zipper state treeInit t = (t, []) treeUp : Zipper state -> Zipper state treeUp (subtree, Crumb lr::bs) = (Branch <| l++[subtree]++r, bs) treeTo : state -> Zipper state -> Zipper state treeTo name (Branch subtrees, bs) = let (l, x::r) = break (\(Leaf name') -> name == name') subtrees in (x, Crumb lr::bs) main = tree |> treeInit |> treeTo "foo" |> show
But this does not allow you to move the focus from the root to the Leaf "baz" and does not allow it to be changed, so add a few more functions (note that all our functions with a zipper can fail, we will change this soon):
(!!) : List a -> Int -> a xs !! i = case List.tail xs of Nothing -> Debug.crash "index out of bounds" Just xs' -> if i == 0 then case List.head xs of Just x -> x else xs' !! (i-1) treeToIndex : Int -> Zipper state -> Zipper state treeToIndex i (Branch subtrees, bs) = (subtrees!!i, Crumb (List.take i subtrees) (List.drop (i+1) subtrees)::bs) treeReplace : state -> Zipper state -> Zipper state treeReplace new (Leaf old, bs) = (Leaf new, bs) main = tree |> treeInit |> treeToIndex 2 |> treeTo "baz" |> treeReplace "xyzzy" |> show
This whole chain of functions can easily work if there is an index larger than the size of the branch, or you are trying to jump from the root, etc. Instead, we should wrap the results in Maybe , so that instead of a failure, Nothing returned. But how do we bind the functions, now that they return Maybe (Zipper state) while they still accept the Zipper state ? Here you are using andThen , which is of type Maybe a -> (a -> Maybe b) -> Maybe b . The full code of your lightning is below:
import Graphics.Element exposing (show) import Maybe exposing (andThen) type Tree state = Branch (List (Tree state)) | Leaf state -- Crumb contains 2 lists: -- leafs/branches to the left of your focus -- leafs/branches to the right of your focus type Crumb state = Crumb (List (Tree state)) (List (Tree state)) type alias Zipper state = (Tree state, List (Crumb state)) tree : Tree String tree = Branch [Leaf "foo",Leaf "bar",Branch [Leaf "baz",Branch [],Leaf "qux"]] break : (a -> Bool) -> List a -> (List a, List a) break p xs = case (List.head xs, List.tail xs) of (Nothing, Nothing) -> ([], []) (Just x, Just xs') -> if px then ([], xs) else let (ys,zs) = break p xs' in (x::ys,zs) treeInit : Tree state -> Zipper state treeInit t = (t, []) treeUp : Zipper state -> Maybe (Zipper state) treeUp (subtree, bs) = case bs of [] -> Nothing Crumb lr::bs' -> Just (Branch <| l++[subtree]++r, bs') treeTo : state -> Zipper state -> Maybe (Zipper state) treeTo name node = case node of (Branch subtrees, bs) -> let (l, x::r) = break (\(Leaf name') -> name == name') subtrees in Just (x, Crumb lr::bs) _ -> Nothing (!!) : List a -> Int -> Maybe a xs !! i = case List.tail xs of Nothing -> Nothing Just xs' -> if i == 0 then List.head xs else xs' !! (i-1) treeToIndex : Int -> Zipper state -> Maybe (Zipper state) treeToIndex i (Branch subtrees, bs) = let newTree = subtrees!!i in case newTree of Nothing -> Nothing Just newTree -> let newCrumb = Crumb (List.take i subtrees) (List.drop (i+1) subtrees) in Just (newTree, newCrumb::bs) treeReplace : state -> Zipper state -> Maybe (Zipper state) treeReplace new node = case node of (Leaf old, bs) -> Just (Leaf new, bs) _ -> Nothing -- the function you're interested in most likely treeUpdate : (state -> state) -> Zipper state -> Maybe (Zipper state) treeUpdate f node = case node of (Leaf name, bs) -> Just (Leaf (f name), bs) _ -> Nothing main = (tree |> treeInit |> treeToIndex 2) `andThen` treeTo "baz" `andThen` treeReplace "xyzzy" `andThen` treeUp `andThen` treeUp |> show
(Feel free to ask questions and clarifications, and I will update and improve this answer)