lens-3.3: Lenses, Folds and Traversals

Portabilitynon-portable
Stabilityexperimental
MaintainerEdward Kmett <ekmett@gmail.com>
Safe HaskellTrustworthy

Control.Lens.Zipper

Contents

Description

This module provides a Zipper with fairly strong type checking guarantees.

The code here is inspired by Brandon Simmons' zippo package, but uses a slightly different approach to represent the Zipper that makes the whole thing look like his breadcrumb trail, and can move side-to-side through traversals.

Some examples types:

Top :> a
represents a trivial Zipper with its focus at the root.
Top :> Tree a :> a
represents a zipper that starts with a Tree and descends in a single step to values of type a.
Top :> Tree a :> Tree a :> Tree a
represents a Zipper into a Tree with an intermediate bookmarked Tree, focusing in yet another Tree.

Since individual levels of a zipper are managed by an arbitrary Traversal, you can move left and right through the Traversal selecting neighboring elements.

>>> zipper ("hello","world") % down _1 % fromWithin traverse % focus .~ 'J' % rightmost % focus .~ 'y' % rezip
("Jelly","world")

This is particularly powerful when compiled with plate, uniplate or biplate for walking down into self-similar children in syntax trees and other structures.

Synopsis

Zippers

data Top Source

This is used to represent the Top of the Zipper.

Every Zipper starts with Top.

e.g. Top :> a is the trivial zipper.

Instances

data p :> a Source

This is the type of a Zipper. It visually resembes a 'breadcrumb trail' as used in website navigation. Each breadcrumb in the trail represents a level you can move up to.

This type operator associates to the left, so you can use a type like

Top :> (String,Double) :> String :> Char

to represent a zipper from (String,Double) down to Char that has an intermediate crumb for the String containing the Char.

Instances

Zipper h b => Zipper (:> h b) c 

zipper :: a -> Top :> aSource

Construct a zipper that can explore anything.

Focusing

focus :: SimpleIndexedLens (Tape (h :> a)) (h :> a) aSource

This Lens views the current target of the zipper.

Horizontal movement

up :: ((a :> b) :> c) -> a :> bSource

Move the zipper up, closing the current level and focusing on the parent element.

down :: SimpleLensLike (Context c c) b c -> (a :> b) -> (a :> b) :> cSource

Step down into a Lens. This is a constrained form of fromWithin for when you know there is precisely one target.

 down :: Simple Lens b c -> (a :> b) -> a :> b :> c
 down :: Simple Iso b c  -> (a :> b) -> a :> b :> c

within :: SimpleLensLike (Bazaar c c) b c -> (a :> b) -> Maybe ((a :> b) :> c)Source

Step down into the leftmost entry of a Traversal.

 within :: Simple Traversal b c -> (a :> b) -> Maybe (a :> b :> c)
 within :: Simple Lens b c      -> (a :> b) -> Maybe (a :> b :> c)
 within :: Simple Iso b c       -> (a :> b) -> Maybe (a :> b :> c)

fromWithin :: SimpleLensLike (Bazaar c c) b c -> (a :> b) -> (a :> b) :> cSource

Unsafely step down into a Traversal that is assumed to be non-empty.

If this invariant is not met then this will usually result in an error!

 fromWithin :: Simple Traversal b c -> (a :> b) -> a :> b :> c
 fromWithin :: Simple Lens b c      -> (a :> b) -> a :> b :> c
 fromWithin :: Simple Iso b c       -> (a :> b) -> a :> b :> c

You can reason about this function as if the definition was:

fromWithin l ≡ fromJust . within l

but it is lazier in such a way that if this invariant is violated, some code can still succeed if it is lazy enough in the use of the focused value.

Lateral movement

left :: (a :> b) -> Maybe (a :> b)Source

Pull the zipper left within the current Traversal.

left1 :: (a :> b) -> a :> bSource

Try to pull the zipper one entry to the left.

If the entry to the left doesn't exist, then stay still.

lefts :: Int -> (h :> a) -> Maybe (h :> a)Source

Try to pull the zipper n entries to the left, returning Nothing if you pull too far and run out of entries.

lefts1 :: Int -> (h :> a) -> h :> aSource

Try to pull the zipper n entries to the left. Stopping at the first entry if you run out of entries.

Passing a negative n will move to -n entries the right, and will return the last entry if you run out of entries.

leftmost :: (a :> b) -> a :> bSource

Move to the left-most position of the current Traversal.

right :: (a :> b) -> Maybe (a :> b)Source

Pull the entry one entry to the right

right1 :: (a :> b) -> a :> bSource

Try to pull the zipper one entry to the right.

If the entry doesn't exist, then stay still.

rights :: Int -> (h :> a) -> Maybe (h :> a)Source

Try to pull the zipper n entries to the right, returning Nothing if you pull too far and run out of entries.

Passing a negative n will move -n entries to the left.

rights1 :: Int -> (h :> a) -> h :> aSource

Try to pull the zipper n entries to the right. Stopping at the last entry if you run out of entries.

Passing a negative number will move to the left and will return the first entry if you run out of entries.

rightmost :: (a :> b) -> a :> bSource

Move to the right-most position of the current Traversal.

goto :: Int -> (a :> b) -> Maybe (a :> b)Source

Move the zipper horizontally to the element in the nth position in the current level. (absolutely indexed, starting with the leftmost as 0)

This returns Nothing if the target element doesn't exist.

goto n = rights n . leftmost

goto1 :: Int -> (a :> b) -> a :> bSource

Move the zipper horizontally to the element in the nth position of the current level. (absolutely indexed, starting with the leftmost as 0)

If the element at that position doesn't exist, then this will clamp to the range 0 <= n < width z and return the element there.

coordinate :: (a :> b) -> IntSource

Return the index into the current Traversal.

goto (coordinate l) l = Just'

width :: (a :> b) -> IntSource

Returns the number of siblings at the current level in the zipper.

width z >= 1

NB: If the current Traversal targets an infinite number of elements then this may not terminate.

Closing the Zipper

rezip :: Zipper h a => (h :> a) -> Zipped h aSource

Close something back up that you opened as a zipper.

type family Zipped h a Source

This represents the type a zipper will have when it is fully Zipped back up.

class Zipper h a Source

This enables us to pull the zipper back up to the Top.

Instances

Zipper Top a 
Zipper h b => Zipper (:> h b) c 

Saving your Progress

data Tape k Source

A Tape is a recorded path through the Traversal chain of a Zipper.

save :: (a :> b) -> Tape (a :> b)Source

Save the current path as as a Tape we can play back later.

restore :: Tape (h :> a) -> Zipped h a -> Maybe (h :> a)Source

Restore ourselves to a previously recorded position precisely.

If the position does not exist, then fail.

restore1 :: Tape (h :> a) -> Zipped h a -> Maybe (h :> a)Source

Restore ourselves to a previously recorded position.

When moving left to right through a Traversal, if this will clamp at each level to the range 0 <= k < width, so the only failures will occur when one of the sequence of downward traversals find no targets.

unsafelyRestore :: Tape (h :> a) -> Zipped h a -> h :> aSource

Restore ourselves to a previously recorded position.

This assumes that nothing has been done in the meantime to affect the existence of anything on the entire path.

Motions left or right are clamped, but all traversals included on the Tape are assumed to be non-empty.

Violate these assumptions at your own risk.