Like a regular old list-zipper, except it can also focus on the holes between elements.
This means you can represent an empty list, or point between two items and plug that hole with a value.
Represents Zipper
over a list with items of type a
. The type t
will,
in practice, always be either Full
or Hole
. When it is Full
, the zipper is
focused on an item. When it is Hole
, you're looking at a hole between elements.
A Zipper Full a
is pointing at an element of type a
.
A Zipper Hole a
is pointing at a hole between a
s.
... Heh.
empty : Zipper Hole a
Create an empty zipper. It's pointing at nothing, there's nothing before it and nothing after it. It's the loneliest of all zippers.
import List.Holey.Zipper as Zipper
Zipper.toList Zipper.empty
--> []
singleton : a -> Zipper Full a
A zipper with a single thing in it. Singleton is just fancy-speak for single thing.
import List.Holey.Zipper as Zipper
Zipper.singleton "wat"
|> Zipper.toList
--> [ "wat" ]
zipper : a -> List a -> Zipper Full a
Construct a zipper from the head of a list and some elements to come after it.
import List.Holey.Zipper as Zipper
Zipper.zipper "foo" []
--> Zipper.singleton "foo"
Zipper.zipper 0 [ 1, 2, 3 ]
|> Zipper.toList
--> [ 0, 1, 2, 3 ]
current : Zipper Full a -> a
Get the value the Zipper
is currently pointing at.
Only applicable to zippers pointing at a value.
import List.Holey.Zipper as Zipper
Zipper.singleton "hi there"
|> Zipper.current
--> "hi there"
Zipper.zipper 1 [ 2, 3, 4 ]
|> Zipper.last
|> Zipper.current
--> 4
before : Zipper t a -> List a
List the things that come before the current location in the zipper.
import List.Holey.Zipper as Zipper
Zipper.zipper 0 [ 1, 2, 3 ]
|> Zipper.next
|> Maybe.andThen Zipper.next
|> Maybe.map Zipper.before
--> Just [ 0, 1 ]
after : Zipper t a -> List a
Conversely, list the things that come after the current location.
import List.Holey.Zipper as Zipper
Zipper.zipper 0 [ 1, 2, 3 ]
|> Zipper.next
|> Maybe.map Zipper.after
--> Just [ 2, 3 ]
toList : Zipper t a -> List a
Flattens the zipper (back) into a list.
import List.Holey.Zipper as Zipper
Zipper.toList Zipper.empty
--> []
Zipper.zipper 123 [ 789 ]
|> Zipper.nextHole
|> Zipper.plug 456
|> Zipper.toList
--> [ 123, 456, 789 ]
next : Zipper t a -> Maybe (Zipper Full a)
Move the zipper to the next item, if there is one.
import List.Holey.Zipper as Zipper
Zipper.zipper 0 [ 1, 2, 3 ]
|> Zipper.next
|> Maybe.map Zipper.current
--> Just 1
This also works from within holes:
Zipper.empty
|> Zipper.insertAfter "foo"
|> Zipper.next
--> Just <| Zipper.singleton "foo"
If there is no next
thing, next
is Nothing
.
Zipper.empty
|> Zipper.next
--> Nothing
Zipper.zipper 0 [ 1, 2, 3 ]
|> Zipper.last
|> Zipper.next
--> Nothing
previous : Zipper t a -> Maybe (Zipper Full a)
Move the zipper to the previous item, if there is one.
import List.Holey.Zipper as Zipper
Zipper.previous Zipper.empty
--> Nothing
Zipper.zipper "hello" [ "holey", "world" ]
|> Zipper.last
|> Zipper.previous
|> Maybe.map Zipper.current
--> Just "holey"
nextHole : Zipper Full a -> Zipper Hole a
Move the zipper to the hole right after the current item. A hole is a whole lot of nothingness, so it's always there.
import List.Holey.Zipper as Zipper
Zipper.zipper "hello" [ "world" ]
|> Zipper.nextHole
|> Zipper.plug "holey"
|> Zipper.toList
--> [ "hello", "holey", "world" ]
previousHole : Zipper Full a -> Zipper Hole a
Move the zipper to the hole right before the current item. Feel free to plug that hole right up!
import List.Holey.Zipper as Zipper
Zipper.singleton "world"
|> Zipper.previousHole
|> Zipper.plug "hello"
|> Zipper.toList
--> [ "hello", "world" ]
first : Zipper Full a -> Zipper Full a
Go to the first element in the Zipper. Note that this only accepts a zipper
that points at a thing, since it would have to return a Maybe
otherwise.
If you want to zip back to the beginning of a zipper pointing at a hole, you can
still use zipper |> previous |> Maybe.map first
import List.Holey.Zipper as Zipper
Zipper.zipper 1 [ 2, 3, 4 ]
|> Zipper.prepend [ 4, 3, 2 ]
|> Zipper.first
|> Zipper.current
--> 4
last : Zipper Full a -> Zipper Full a
Go to the last element of a zipper. Same warnings as for first
apply.
import List.Holey.Zipper as Zipper
Zipper.zipper 1 [ 2, 3, 4 ]
|> Zipper.last
|> Zipper.current
--> 4
beforeFirst : Zipper t a -> Zipper Hole a
Go to the hole before the first element. Remember that holes surround everything! They are everywhere.
import List.Holey.Zipper as Zipper
Zipper.zipper 1 [ 3, 4 ]
-- now we're after 1
|> Zipper.nextHole
-- plug that hole
|> Zipper.plug 2
-- back to _before_ the first element
|> Zipper.beforeFirst
-- put something in that hole
|> Zipper.plug 0
-- and check the result
|> Zipper.toList
--> [ 0, 1, 2, 3, 4 ]
afterLast : Zipper t a -> Zipper Hole a
Go to the hole after the end of the zipper. Into the nothingness.
findForward : (a -> Basics.Bool) -> Zipper t a -> Maybe (Zipper Full a)
Find the first element in the zipper the matches a predicate, returning a zipper pointing at that thing if it was found. When provided with a zipper pointing at a thing, that thing is also checked.
This start from the current location, and searches towards the end.
findBackward : (a -> Basics.Bool) -> Zipper t a -> Maybe (Zipper Full a)
Find the first element in the zipper matching a predicate, moving backwards from the current position.
map : (a -> b) -> Zipper t a -> Zipper t b
Execute a function on every item in the zipper.
import List.Holey.Zipper as Zipper
Zipper.zipper "first" [ "second", "third" ]
|> Zipper.map String.toUpper
|> Zipper.toList
--> [ "FIRST", "SECOND", "THIRD" ]
mapCurrent : (a -> a) -> Zipper t a -> Zipper t a
Execute a function on the current item in the zipper, when pointing at an item.
import List.Holey.Zipper as Zipper
Zipper.zipper "first" [ "second", "third" ]
|> Zipper.mapCurrent String.toUpper
|> Zipper.toList
--> [ "FIRST", "second", "third" ]
mapBefore : (a -> a) -> Zipper t a -> Zipper t a
Execute a function on all the things that came before the current location.
import List.Holey.Zipper as Zipper
Zipper.zipper "first" [ "second" ]
|> Zipper.nextHole
|> Zipper.mapBefore String.toUpper
|> Zipper.toList
--> [ "FIRST", "second" ]
mapAfter : (a -> a) -> Zipper t a -> Zipper t a
Execute a function on all the things that come after the current location.
mapParts : { before : a -> b, current : a -> b, after : a -> b } -> Zipper t a -> Zipper t b
Execute a triplet of functions on the different parts of a zipper - what came before, what comes after, and the current thing if there is one.
import List.Holey.Zipper as Zipper
Zipper.zipper "first" [ "second" ]
|> Zipper.nextHole
|> Zipper.plug "one-and-a-halfth"
|> Zipper.mapParts
{ before = (++) "before: "
, current = (++) "current: "
, after = (++) "after: "
}
|> Zipper.toList
--> [ "before: first"
--> , "current: one-and-a-halfth"
--> , "after: second"
--> ]
plug : a -> Zipper Hole a -> Zipper Full a
Plug a hole-y zipper.
import List.Holey.Zipper as Zipper
Zipper.plug "plug" Zipper.empty
--> Zipper.singleton "plug"
remove : Zipper Full a -> Zipper Hole a
Punch a hole into the zipper by removing an element entirely. You can think of this as collapsing the holes around the element into a single hole, but honestly the holes are everywhere.
import List.Holey.Zipper as Zipper
Zipper.zipper "hello" [ "holey", "world" ]
|> Zipper.next
|> Maybe.map Zipper.remove
|> Maybe.map Zipper.toList
--> Just [ "hello", "world" ]
append : List a -> Zipper t a -> Zipper t a
Append a bunch of items after the zipper. This appends all the way at the end.
import List.Holey.Zipper as Zipper
Zipper.zipper 123 [ 456 ]
|> Zipper.append [ 789, 0 ]
|> Zipper.toList
--> [ 123, 456, 789, 0 ]
prepend : List a -> Zipper t a -> Zipper t a
Prepend a bunch of things to the zipper. All the way before anything else.
import List.Holey.Zipper as Zipper
Zipper.zipper 1 [ 2, 3, 4 ]
|> Zipper.last
|> Zipper.prepend [ 5, 6, 7 ]
|> Zipper.toList
--> [ 5, 6, 7, 1, 2, 3, 4 ]
insertAfter : a -> Zipper t a -> Zipper t a
Insert something after the current location.
import List.Holey.Zipper as Zipper
Zipper.empty
|> Zipper.insertAfter "hello"
|> Zipper.toList
--> [ "hello" ]
Zipper.zipper 123 [ 789 ]
|> Zipper.insertAfter 456
|> Zipper.toList
--> [ 123, 456, 789 ]
insertBefore : a -> Zipper t a -> Zipper t a
Insert something before the current location.
import List.Holey.Zipper as Zipper
Zipper.empty
|> Zipper.insertBefore "hello"
|> Zipper.toList
--> [ "hello" ]
Zipper.singleton 123
|> Zipper.insertBefore 456
|> Zipper.toList
--> [ 456, 123 ]