Portability | non-portable |
---|---|
Stability | experimental |
Maintainer | Edward Kmett <ekmett@gmail.com> |
Safe Haskell | Trustworthy |
This module provides internal types and functions used in the implementation
of Control.Lens.Zipper
. You shouldn't need to import it directly, and the
exported types can be used to break Zipper
invariants.
- data Top
- data h :> a = Zipper (Coil h a) !Int [a] a [a]
- type Zipper = :>
- type family Zipped h a
- data Coil where
- focus :: SimpleIndexedLens (Tape (h :> a)) (h :> a) a
- zipper :: a -> Top :> a
- tooth :: (h :> a) -> Int
- upward :: ((h :> s) :> a) -> h :> s
- rightward :: MonadPlus m => (h :> a) -> m (h :> a)
- leftward :: MonadPlus m => (h :> a) -> m (h :> a)
- leftmost :: (a :> b) -> a :> b
- rightmost :: (a :> b) -> a :> b
- tug :: (a -> Maybe a) -> a -> a
- tugs :: (a -> Maybe a) -> Int -> a -> a
- farthest :: (a -> Maybe a) -> a -> a
- jerks :: Monad m => (a -> m a) -> Int -> a -> m a
- teeth :: (h :> a) -> Int
- jerkTo :: MonadPlus m => Int -> (h :> a) -> m (h :> a)
- tugTo :: Int -> (h :> a) -> h :> a
- downward :: SimpleLensLike (Context a a) s a -> (h :> s) -> (h :> s) :> a
- within :: MonadPlus m => SimpleLensLike (Bazaar a a) s a -> (h :> s) -> m ((h :> s) :> a)
- withins :: SimpleLensLike (Bazaar a a) s a -> (h :> s) -> [(h :> s) :> a]
- fromWithin :: SimpleLensLike (Bazaar a a) s a -> (h :> s) -> (h :> s) :> a
- class Zipping h a where
- rezip :: Zipping h a => (h :> a) -> Zipped h a
- focusedContext :: Zipping h a => (h :> a) -> Context a a (Zipped h a)
- data Tape k where
- saveTape :: (h :> a) -> Tape (h :> a)
- restoreTape :: MonadPlus m => Tape (h :> a) -> Zipped h a -> m (h :> a)
- restoreNearTape :: MonadPlus m => Tape (h :> a) -> Zipped h a -> m (h :> a)
- unsafelyRestoreTape :: Tape (h :> a) -> Zipped h a -> h :> a
- peel :: Coil h a -> Track h a
- data Track where
- restoreTrack :: MonadPlus m => Track h a -> Zipped h a -> m (h :> a)
- restoreNearTrack :: MonadPlus m => Track h a -> Zipped h a -> m (h :> a)
- unsafelyRestoreTrack :: Track h a -> Zipped h a -> h :> a
- reverseList :: [a] -> [a]
Documentation
>>>
import Control.Lens
>>>
import Data.Char
Zippers
This is the type of a Zipper
. It visually resembles 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 (
down to String
,Double
)Char
that has an intermediate
crumb for the String
containing the Char
.
You can construct a zipper into *any* data structure with zipper
.
>>>
:t zipper (Just "hello")
zipper (Just "hello") :: Top :> Maybe [Char]
You can repackage up the contents of a zipper with rezip
.
>>>
rezip $ zipper 42
42
The combinators in this module provide lot of things you can do to the zipper while you have it open.
Note that a value of type h
doesn't actually contain a value
of type :>
s :>
ah
-- as we descend into a level, the previous level is
unpacked and stored in :>
sCoil
form. Only one value of type _
exists
at any particular time for any particular :>
_Zipper
.
upward :: ((h :> s) :> a) -> h :> sSource
Move the Zipper
upward
, closing the current level and focusing on the parent element.
NB: Attempts to move upward from the Top
of the Zipper
will fail to typecheck.
>>>
:t zipper ("hello","world") & downward _1 & fromWithin traverse & upward
zipper ("hello","world") & downward _1 & fromWithin traverse & upward :: (Top :> ([Char], [Char])) :> [Char]
rightward :: MonadPlus m => (h :> a) -> m (h :> a)Source
Jerk the Zipper
one tooth
to the rightward
within the current Lens
or Traversal
.
Attempts to move past the start of the current Traversal
(or trivially, the current Lens
)
will return Nothing
.
>>>
isNothing $ zipper "hello" & rightward
True
>>>
zipper "hello" & fromWithin traverse & rightward <&> view focus
'e'
>>>
zipper "hello" & fromWithin traverse & rightward <&> focus .~ 'u' <&> rezip
"hullo"
>>>
rezip $ zipper (1,2) & fromWithin both & tug rightward & focus .~ 3
(1,3)
tug :: (a -> Maybe a) -> a -> aSource
This allows you to safely 'tug leftward' or 'tug rightward' on a zipper
. This
will attempt the move, and stay where it was if it fails.
The more general signature allows its use in other circumstances, however.
tug
f x ≡fromMaybe
a (f a)
>>>
fmap rezip $ zipper "hello" & within traverse <&> tug leftward <&> focus .~ 'j'
"jello"
>>>
fmap rezip $ zipper "hello" & within traverse <&> tug rightward <&> focus .~ 'u'
"hullo"
tugs :: (a -> Maybe a) -> Int -> a -> aSource
This allows you to safely
or tug
leftward
multiple times on a tug
rightward
zipper
,
moving multiple steps in a given direction and stopping at the last place you
couldn't move from. This lets you safely move a zipper, because it will stop at either end.
>>>
fmap rezip $ zipper "stale" & within traverse <&> tugs rightward 2 <&> focus .~ 'y'
"style"
>>>
rezip $ zipper "want" & fromWithin traverse & tugs rightward 2 & focus .~ 'r' & tugs leftward 100 & focus .~ 'c'
"cart"
farthest :: (a -> Maybe a) -> a -> aSource
Move in a direction as far as you can go, then stop there.
This repeatedly applies a function until it returns Nothing, and then returns the last answer.
>>>
fmap rezip $ zipper ("hello","world") & downward _1 & within traverse <&> rightmost <&> focus .~ 'a'
("hella","world")
>>>
rezip $ zipper ("hello","there") & fromWithin (both.traverse) & rightmost & focus .~ 'm'
("hello","therm")
jerks :: Monad m => (a -> m a) -> Int -> a -> m aSource
This allows for you to repeatedly pull a zipper
in a given direction, failing if it falls off the end.
>>>
isNothing $ zipper "hello" & within traverse >>= jerks rightward 10
True
>>>
fmap rezip $ zipper "silly" & within traverse >>= jerks rightward 3 <&> focus .~ 'k'
"silky"
teeth :: (h :> a) -> IntSource
Returns the number of siblings at the current level in the zipper
.
teeth
z>=
1
NB: If the current Traversal
targets an infinite number of elements then this may not terminate.
>>>
zipper ("hello","world") & teeth
1
>>>
zipper ("hello","world") & fromWithin both & teeth
2
>>>
zipper ("hello","world") & downward _1 & teeth
1
>>>
zipper ("hello","world") & downward _1 & fromWithin traverse & teeth
5
>>>
zipper ("hello","world") & fromWithin (_1.traverse) & teeth
5
>>>
zipper ("hello","world") & fromWithin (both.traverse) & teeth
10
jerkTo :: MonadPlus m => Int -> (h :> a) -> m (h :> a)Source
Move the Zipper
horizontally to the element in the n
th position in the
current level, absolutely indexed, starting with the farthest
leftward
as 0
.
This returns Nothing
if the target element doesn't exist.
jerkTo
n ≡jerks
rightward
n .farthest
leftward
>>>
isNothing $ zipper "not working." & jerkTo 20
True
tugTo :: Int -> (h :> a) -> h :> aSource
Move the Zipper
horizontally to the element in the n
th position of the
current level, absolutely indexed, starting with the farthest
leftward
as 0
.
If the element at that position doesn't exist, then this will clamp to the range 0 <= n <
.
teeth
tugTo
n ≡tugs
rightward
n .farthest
leftward
>>>
rezip $ zipper "not working." & fromWithin traverse & tugTo 100 & focus .~ '!' & tugTo 1 & focus .~ 'u'
"nut working!"
withins :: SimpleLensLike (Bazaar a a) s a -> (h :> s) -> [(h :> s) :> a]Source
Step down into every entry of a Traversal
simultaneously.
>>>
zipper ("hello","world") & withins both >>= leftward >>= withins traverse >>= rightward <&> focus %~ toUpper <&> rezip
[("hEllo","world"),("heLlo","world"),("helLo","world"),("hellO","world")]
withins
::Simple
Traversal
s a -> (h :> s) -> [h :> s :> a]withins
::Simple
Lens
s a -> (h :> s) -> [h :> s :> a]withins
::Simple
Iso
s a -> (h :> s) -> [h :> s :> a]
fromWithin :: SimpleLensLike (Bazaar a a) s a -> (h :> s) -> (h :> s) :> aSource
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
s a -> (h :> s) -> h :> s :> afromWithin
::Simple
Lens
s a -> (h :> s) -> h :> s :> afromWithin
::Simple
Iso
s a -> (h :> s) -> h :> s :> a
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.
rezip :: Zipping h a => (h :> a) -> Zipped h aSource
Close something back up that you opened as a Zipper
.
Tapes
saveTape :: (h :> a) -> Tape (h :> a)Source
Save the current path as as a Tape
we can play back later.
restoreTape :: MonadPlus m => Tape (h :> a) -> Zipped h a -> m (h :> a)Source
Restore ourselves to a previously recorded position precisely.
If the position does not exist, then fail.
restoreNearTape :: MonadPlus m => Tape (h :> a) -> Zipped h a -> m (h :> a)Source
Restore ourselves to a location near our previously recorded position.
When moving left to right through a Traversal
, if this will clamp at each level to the range 0 <= k < teeth
,
so the only failures will occur when one of the sequence of downward traversals find no targets.
unsafelyRestoreTape :: 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 leftward or rightward are clamped, but all traversals included on the Tape
are assumed to be non-empty.
Violate these assumptions at your own risk!
Tracks
peel :: Coil h a -> Track h aSource
This is used to peel off the path information from a Coil
for use when saving the current path for later replay.
restoreTrack :: MonadPlus m => Track h a -> Zipped h a -> m (h :> a)Source
Restore ourselves to a previously recorded position precisely.
If the position does not exist, then fail.
restoreNearTrack :: MonadPlus m => Track h a -> Zipped h a -> m (h :> a)Source
Restore ourselves to a location near our previously recorded position.
When moving leftward to rightward through a Traversal
, if this will clamp at each level to the range 0 <= k < teeth
,
so the only failures will occur when one of the sequence of downward traversals find no targets.
unsafelyRestoreTrack :: Track 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 leftward or rightward are clamped, but all traversals included on the Tape
are assumed to be non-empty.
Violate these assumptions at your own risk!
Helper functions
reverseList :: [a] -> [a]Source
Reverse a list.
GHC doesn't optimize reverse []
into []
, so we'll nudge it with our own
reverse function.
This is relevant when descending into a lens, for example -- we know the unzipped part of the level will be empty.