zippo-0.1: Data/Lens/Zipper.hs
{-# LANGUAGE TypeOperators
, MultiParamTypeClasses, FlexibleContexts
, TypeFamilies
, FlexibleInstances #-}
module Data.Lens.Zipper (
{- |
We provide a simple, heterogenous, fully type-checked, generic zipper
implementation. This flavor of zipper doesn\'t use \"left\" and \"right\" for
navigation, and instead relies on lenses to indicate a child type to \"move
to\".
-}
-- * Zipper type
Zipper
-- ** Zipper history
, Top , (:>) , Hist
-- * Zipper operations
, zipper , close
-- ** Motions
, move , moveP , moveUp
-- ** Focus
, focus , setf , modf
) where
{- TODO
- - excellent rewrite rules
- - consider a newtype-wrapped submodule encapsulating monad return value
- - more advanced motions a.la. pez?
- - better demos
-}
import Data.Yall.Lens
import Control.Monad.Identity
-- | Our zipper type, parameterized by a 'focus' and \"history stack\",
-- supporting completely type-checked zipper operations.
data Zipper st b = Zipper { hist :: st b , viewf :: b }
data (:>) st b c = Snoc (st b) (c -> b)
data Top a = Top
-- | A lens on the focus of the zipper.
focus :: Zipper st b :-> b
focus = lens viewf $ \z b-> z{ viewf = b }
-- | Set the zipper focus
--
-- > setf = set focus
setf :: Zipper st b -> b -> Zipper st b
setf = set focus
-- | Modify the zipper focus
--
-- > modf = modify focus
modf :: (b -> b) -> Zipper st b -> Zipper st b
modf = modify focus
-- | \"enter\" a data type. Move the 'focus' with 'move' and 'moveUp'. Exit
-- the zipper with 'close'.
--
-- > zipper = Zipper Top
zipper :: a -> Zipper Top a
zipper = Zipper Top
class Hist st a c where
runHist :: st c -> (c -> a)
-- our only use of TypeFamilies. Thanks to Daniel Wagner for this trick:
instance a ~ b => Hist Top a b where
runHist _ = id
instance (Hist st a b) => Hist ((:>) st b) a c where
runHist (Snoc st' cb) = runHist st' . cb
-- | exit the zipper, rebuilding the structure @a@:
--
-- > close (Zipper st b) = runHist st b
close :: (Hist st a b)=> Zipper st b -> a
close (Zipper st b) = runHist st b
-- | navigate to a child element indicated by the passed lens, returning the
-- new Zipper in the monad @m@. This will be 'Maybe' when the standard (':~>')
-- Lens is used. For pure lenses, use 'moveP'.
move :: (Monad m)=> LensM m b c -> Zipper st b -> m (Zipper (st :> b) c)
move l (Zipper st a) =
liftM (uncurry $ Zipper . Snoc st . fmap runIdentity) (runLens l a)
-- | navigate to a child element indicated by the passed pure lens
--
-- > moveP l = runIdentity . move l
moveP :: (b :-> c) -> Zipper st b -> Zipper (st :> b) c
moveP l = runIdentity . move l
-- | navigate up a level in a zipper not already at 'Top'
--
-- > moveUp (Zipper (Snoc st cont) c) = Zipper st $ cont c
moveUp :: Zipper (st :> b) c -> Zipper st b
moveUp (Zipper (Snoc st cont) c) = Zipper st $ cont c