packages feed

nonempty-zipper (empty) → 1.0.0.0

raw patch · 6 files changed

+388/−0 lines, 6 filesdep +Globdep +basedep +comonad

Dependencies added: Glob, base, comonad, deepseq, doctest, safe

Files

+ CHANGELOG.md view
@@ -0,0 +1,7 @@+## [_Unreleased_](https://github.com/frontrowed/faktory_worker_haskell/compare/v1.0.0.0...main)++None++## [v1.0.0.0](https://github.com/frontrowed/faktory_worker_haskell/tree/v1.0.0.0)++Initial release.
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2020 Renaissance Learning Inc++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,20 @@+# nonempty-zipper++A non-empty comonadic list zipper++## Why not another Zipper implementation?++A [Hoogle search][hoogle-search] reveals various implementations of zippers.++The two that have the most similar feature sets to `nonempty-zipper` are+[list-zipper][list-zipper] and [non-empty-zipper][non-empty-zipper].++[list-zipper][list-zipper] has a solid lens interface, and a ton of instances,+however it's lacking documentation and an `NFData` instance.++[non-empty-zipper][non-empty-zipper] is most similar to this package but it+appears to be unmaintained (e.g. `base` has drifted).++  [hoogle-search]: https://hoogle.haskell.org/?hoogle=zipper+  [list-zipper]: https://hackage.haskell.org/package/list-zipper+  [non-empty-zipper]: https://hackage.haskell.org/package/non-empty-zipper
+ doctest/Main.hs view
@@ -0,0 +1,9 @@+module Main (main) where++import System.FilePath.Glob+import Test.DocTest++main :: IO ()+main = do+  paths <- globDir1 (compile "**/*.hs") "library"+  doctest paths
+ library/Data/List/NonEmpty/Zipper.hs view
@@ -0,0 +1,283 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}++module Data.List.NonEmpty.Zipper+  ( Zipper++  -- * Accessors+  , lefts+  , rights+  , current++  -- * Traversal+  , left+  , right+  , findLeft+  , findRight+  , start+  , end++  -- * Construction+  , fromNonEmpty+  , fromNonEmptyEnd++  -- ** Update+  , replace+  , delete+  , push+  , pop+  , shift+  , unshift+  , reverse++  -- * Predicates+  , isStart+  , isEnd+  )+where++import Prelude hiding (reverse)+import qualified Prelude++import Control.Comonad+import Control.DeepSeq (NFData)+import qualified Data.List.NonEmpty as NE+import Data.Maybe (fromMaybe)+import GHC.Generics (Generic)+import Safe (headMay, tailMay)++data Zipper a = Zipper [a] a [a]+  deriving stock (Eq, Show, Functor, Generic)+  deriving anyclass (NFData)++instance Foldable Zipper where+  foldMap f (Zipper ls x rs) = foldMap f (Prelude.reverse ls) `mappend` f x `mappend` foldMap f rs++instance Traversable Zipper where+  traverse f (Zipper ls x rs) =+    Zipper+      <$> (Prelude.reverse <$> traverse f (Prelude.reverse ls))+      <*> f x+      <*> traverse f rs++-- | The list zipper is a basic comonad+--+-- This instance allows us to create a zipper of all possible states of+-- traversing the zipper with 'left' and 'right'.+--+-- >>> duplicate $ fromNonEmpty $ NE.fromList [1, 2, 3]+-- Zipper [] (Zipper [] 1 [2,3]) [Zipper [1] 2 [3],Zipper [2,1] 3 []]+--+instance Comonad Zipper where+  extract = current+  duplicate z =+    let dupWith f r =+          case f r of+            Nothing -> [r]+            Just x -> r:dupWith f x+    in Zipper+      (maybe [] (dupWith left) $ left z)+      z+      (maybe [] (dupWith right) $ right z)++-- | Get the current focus of the @'Zipper'@ cursor+--+-- This is a synonym for 'Control.Comonad.extract'+--+-- >>> current . fromNonEmpty $ NE.fromList [1, 2, 3]+-- 1+--+current :: Zipper a -> a+current (Zipper _ curr _) = curr++-- | Get all values on the left of the cursor+--+-- >>> lefts . fromNonEmptyEnd $ NE.fromList [1, 2, 3]+-- [1,2]+--+lefts :: Zipper a -> [a]+lefts (Zipper ls _ _) = Prelude.reverse ls++-- | Get all values on the right of the cursor+--+-- >>> rights . fromNonEmpty $ NE.fromList [1, 2, 3]+-- [2,3]+--+rights :: Zipper a -> [a]+rights (Zipper _ _ rs) = rs+++-- | Move the current focus of the cursor to the left+--+-- >>> left . fromNonEmptyEnd $ NE.fromList [1, 2, 3]+-- Just (Zipper [1] 2 [3])+--+left :: Zipper a -> Maybe (Zipper a)+left (Zipper ps curr ns) = do+  newCurr <- headMay ps+  pure $ Zipper (fromMaybe [] $ tailMay ps) newCurr (curr : ns)++-- | Move the current focus of the cursor to the right+--+-- >>> right . fromNonEmpty $ NE.fromList [1, 2, 3]+-- Just (Zipper [1] 2 [3])+--+right :: Zipper a -> Maybe (Zipper a)+right (Zipper ps curr ns) = do+  newCurr <- headMay ns+  pure $ Zipper (curr : ps) newCurr (fromMaybe [] $ tailMay ns)++-- | Move the current focus of the cursor to the first occurence of a value on the left+--+-- >>> findLeft 2 . fromNonEmptyEnd $ NE.fromList [2, 1, 2, 1, 1, 3]+-- Just (Zipper [1,2] 2 [1,1,3])+--+findLeft :: Eq a => a -> Zipper a -> Maybe (Zipper a)+findLeft target z@(Zipper ps curr ns)+  | curr == target = Just z+  | otherwise = case ps of+    [] -> Nothing+    (x : xs) -> findLeft target (Zipper xs x (curr : ns))++-- | Move the current focus of the cursor to the first occurence of a value on the right+--+-- >>> findRight 3 . fromNonEmpty $ NE.fromList [2, 1, 3, 1, 1, 3]+-- Just (Zipper [1,2] 3 [1,1,3])+--+findRight :: Eq a => a -> Zipper a -> Maybe (Zipper a)+findRight target z@(Zipper ps curr ns)+  | curr == target = Just z+  | otherwise = case ns of+    [] -> Nothing+    (x : xs) -> findRight target (Zipper (curr : ps) x xs)++-- | Move the current focus of the cursor to the start of the @'Zipper'@+--+-- >>> start . fromNonEmptyEnd $ NE.fromList [1, 2, 3]+-- Zipper [] 1 [2,3]+--+start :: Zipper a -> Zipper a+start z+  | isStart z = z+  | otherwise = fromNonEmpty $ toNonEmpty z++-- | Move the current focus of the cursor to the end of the @'Zipper'@+--+-- >>> end . fromNonEmpty $ NE.fromList [1, 2, 3]+-- Zipper [2,1] 3 []+--+end :: Zipper a -> Zipper a+end z+  | isEnd z = z+  | otherwise = fromNonEmptyEnd $ toNonEmpty z+++fromNonEmpty :: NE.NonEmpty a -> Zipper a+fromNonEmpty ne = Zipper [] (NE.head ne) (NE.tail ne)++fromNonEmptyEnd :: NE.NonEmpty a -> Zipper a+fromNonEmptyEnd ne = Zipper (NE.tail reversed) (NE.head reversed) []+  where reversed = NE.reverse ne++toNonEmpty :: Zipper a -> NE.NonEmpty a+toNonEmpty (Zipper ls x rs) = NE.fromList $ Prelude.reverse ls ++ [x] ++ rs+++-- | Replace the current item under the curosr+--+-- >>> replace 4 . fromNonEmpty $ NE.fromList [1, 2, 3]+-- Zipper [] 4 [2,3]+--+replace :: a -> Zipper a -> Zipper a+replace x (Zipper ls _ rs) = Zipper ls x rs++-- | Delete the item currently under the cursor+--+-- The item currently under the cursor is removed. The cursors focus will move+-- right. If at the end of the @'Zipper'@ the cursor will move left.+--+-- >>> delete . fromNonEmpty $ NE.fromList [1, 2, 3]+-- Just (Zipper [] 2 [3])+--+-- >>> delete . fromNonEmptyEnd $ NE.fromList [1, 2, 3]+-- Just (Zipper [1] 2 [])+--+delete :: Zipper a -> Maybe (Zipper a)+delete (Zipper [] _ []) = Nothing+delete (Zipper ls _ (r : rs)) = Just $ Zipper ls r rs+delete (Zipper (l : ls) _ rs) = Just $ Zipper ls l rs++-- | Insert a value to the left of the cursor+--+-- >>> push 0 . fromNonEmpty $ NE.fromList [1, 2, 3]+-- Zipper [0] 1 [2,3]+--+push :: a -> Zipper a -> Zipper a+push l (Zipper ls x rs) = Zipper (l : ls) x rs++-- | Remove a value to the left of the cursor+--+-- >>> pop . fromNonEmpty $ NE.fromList [1, 2, 3]+-- (Zipper [] 1 [2,3],Nothing)+--+-- >>> pop . fromNonEmptyEnd $ NE.fromList [1, 2, 3]+-- (Zipper [1] 3 [],Just 2)+--+pop :: Zipper a -> (Zipper a, Maybe a)+pop (Zipper [] x rs) = (Zipper [] x rs, Nothing)+pop (Zipper (l : ls) x rs) = (Zipper ls x rs, Just l)++-- | Remove a value to the right of the cursor+--+-- >>> shift . fromNonEmptyEnd $ NE.fromList [1, 2, 3]+-- (Zipper [2,1] 3 [],Nothing)+--+-- >>> shift . fromNonEmpty $ NE.fromList [1, 2, 3]+-- (Zipper [] 1 [3],Just 2)+--+shift :: Zipper a -> (Zipper a, Maybe a)+shift (Zipper ls x []) = (Zipper ls x [], Nothing)+shift (Zipper ls x (r : rs)) = (Zipper ls x rs, Just r)++-- | Insert a value to the right of the cursor+--+-- >>> unshift 4 . fromNonEmpty $ NE.fromList [1, 2, 3]+-- Zipper [] 1 [4,2,3]+--+unshift :: a -> Zipper a -> Zipper a+unshift r (Zipper ls x rs) = Zipper ls x (r : rs)++-- | Reverse the zipper keeping the cursor focus intact+--+-- >>> reverse . fromNonEmpty $ NE.fromList [1, 2, 3]+-- Zipper [2,3] 1 []+--+reverse :: Zipper a -> Zipper a+reverse (Zipper ls x rs) = Zipper rs x ls+++-- | Determine if the @'Zipper'@ is at the beginning+--+-- >>> isStart . fromNonEmpty $ NE.fromList [1, 2, 3]+-- True+--+-- >>> isStart . fromNonEmptyEnd $ NE.fromList [1, 2, 3]+-- False+--+isStart :: Zipper a -> Bool+isStart (Zipper [] _ _) = True+isStart _ = False++-- | Determine if the @'Zipper'@ is at the end+--+-- >>> isEnd . fromNonEmptyEnd $ NE.fromList [1, 2, 3]+-- True+--+-- >>> isEnd . fromNonEmpty $ NE.fromList [1, 2, 3]+-- False+--+isEnd :: Zipper a -> Bool+isEnd (Zipper _ _ []) = True+isEnd _ = False
+ nonempty-zipper.cabal view
@@ -0,0 +1,48 @@+cabal-version:      1.12+name:               nonempty-zipper+version:            1.0.0.0+license:            MIT+license-file:       LICENSE+copyright:          2021 Renaissance Learning Inc+maintainer:         engineering@freckle.com+author:             Freckle R&D+homepage:           https://github.com/freckle/nonempty-zipper#readme+bug-reports:        https://github.com/freckle/nonempty-zipper/issues+synopsis:           A non-empty comonadic list zipper+description:        A non-empty comonadic list zipper+category:           Data, Data Structures+build-type:         Simple+extra-source-files:+    README.md+    CHANGELOG.md++source-repository head+    type:     git+    location: https://github.com/freckle/nonempty-zipper++library+    exposed-modules:  Data.List.NonEmpty.Zipper+    hs-source-dirs:   library+    other-modules:    Paths_nonempty_zipper+    default-language: Haskell2010+    ghc-options:      -Wall+    build-depends:+        base >=4.14.1.0 && <4.15,+        comonad >=5.0.8 && <5.1,+        deepseq >=1.4.4.0 && <1.5,+        safe >=0.3.19 && <0.4++test-suite doctest+    type:             exitcode-stdio-1.0+    main-is:          Main.hs+    hs-source-dirs:   doctest+    other-modules:    Paths_nonempty_zipper+    default-language: Haskell2010+    ghc-options:      -Wall+    build-depends:+        Glob >=0.10.1 && <0.11,+        base >=4.14.1.0 && <4.15,+        comonad >=5.0.8 && <5.1,+        deepseq >=1.4.4.0 && <1.5,+        doctest >=0.16.3 && <0.17,+        safe >=0.3.19 && <0.4