packages feed

reverse-list (empty) → 0.2.0

raw patch · 6 files changed

+382/−0 lines, 6 filesdep +basedep +containersdep +contiguous

Dependencies added: base, containers, contiguous, deepseq

Files

+ CHANGELOG.md view
@@ -0,0 +1,14 @@+# Revision history for reverse-list++## 0.2.0 -- 2022-01-27++* Remove `Foldable` instance until I determine the correct semantics.+* Add `init`, `last`.+* Add `Data.List.Cons` module and `Tsil` synonym for "symmetry".+* Rename `Data.List.RList` to `Data.List.Snoc`+* `RList.{safeLast,safeInit}`+* `RList.toSet`++## 0.1.0 -- 2021-12-27++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Okuno Zankoku (c) 2021++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Okuno Zankoku nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,26 @@+# reverse-list++The key idea of this library is to leverage the type system to control the performance characteristics of list-manipulation code.+It defines the type `RList`, which is a snoc-list rather than a cons-list.+It also creates a symmetric module for cons-lists, which focuses on the efficient and safe use of linked lists.++Admittedly, parsing `String`s as in this example is bad for performance anyway, but the potential bugs are the same for any use of lists as accumulators:+```+import qualified Data.List.Snoc as RList++parseSqlString :: String -> Maybe String+parseSqlString str0 = case str0 of+  '\'':rest -> loop "" rest+  _ -> Nothing+  where+  loop :: RList Char -> [Char] -> Maybe [Char]+  loop acc [] = Nothing+  -- it is impossible to accidentally return the accumulator without reversing+  loop acc "\'" = Just $ Rlist.toList acc+  loop acc ('\'':'\'':rest) = loop (Snoc acc '\'') rest+  loop acc (c:rest) = loop (Snoc acc c) rest+```++Currently, we only support the basic introduction/elimination forms (though reasonably ergonomically), and conversions.+Additional functions should certainly be exposed, after due consideration is given to their semantics, including performance.+If you run into anything you think deserved to be exported, open an issue or a pull request and I'll be happy to get it done.
+ reverse-list.cabal view
@@ -0,0 +1,31 @@+cabal-version: 3.0+name: reverse-list+version: 0.2.0+synopsis: reversed lists/snoc lists+description: The key idea of this library is to leverage the type system to control the performance characteristics of list-manipulation code.+  It defines the type `RList`, which is a snoc-list rather than a cons-list.+  It also creates a symmetric module for cons-lists, which focuses on the efficient and safe use of linked lists.+  See README.md for more information.+category: Data+homepage: https://github.com/edemko/reverse-list+bug-reports: https://github.com/edemko/reverse-list/issues+author: Eric Demko+maintainer: zankoku.okuno@gmail.com+copyright: 2021 Eric Demko+license: BSD-3-Clause+license-file: LICENSE+extra-source-files: README.md, CHANGELOG.md++library+  hs-source-dirs: src+  exposed-modules:+    Data.List.Cons+    Data.List.Snoc+  -- other-modules:+  build-depends:+    , base >=4.14.3 && <4.16+    , containers >=0.6 && <0.7+    , contiguous >= 0.6 && <0.7+    , deepseq >=1.4 && <1.5+  default-language: Haskell2010+  ghc-options: -O2 -Wall -Wunticked-promoted-constructors
+ src/Data/List/Cons.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ViewPatterns #-}++-- | This module exists primarily for symmetry with "Data.List.Snoc"+-- However, it can also be used in place of the "Prelude" list type:+--+-- This module only exports functions that are efficient on linked lists. Many+-- functions on that type ('Prelude.last' 'Data.List.isSuffixOf') though+-- technically implementable, do not represent the intended use of a linked list+-- in terms of performance.+--+-- Additionally, this module does not export any partial functions: 'head' and+-- 'tail' return their results under a 'Maybe'.+module Data.List.Cons+  ( List+  , pattern Nil+  , pattern Cons+  , nil+  , cons+  , uncons+  , singleton+  , head+  , tail+  ) where++import Prelude hiding (head,tail)++-- | As a counterpart to 'Data.List.Snoc.RList'/'Data.List.Snoc.Tsil'.+type List = ([])++{-# COMPLETE Nil, Cons #-}++-- | An empty 'List', such as 'nil'.+pattern Nil :: List a+pattern Nil = []++-- | The 'List' consisting of head and tail elements, such as created by 'cons'.+pattern Cons :: a -> List a -> List a+pattern Cons x xs <- (uncons -> Just (x, xs))+  where Cons = cons++-- | The empty 'List'.+nil :: List a+{-# INLINABLE nil #-}+nil = []++-- | @O(1)@ Append an element.+--+-- If you are looking for @snoc@, you should use an 'RList', or a finite sequence/queue type.+cons :: a -> List a -> List a+{-# INLINABLE cons #-}+cons = (:)++-- | @O(1)@ Access the first element and trailing portion of the list.+-- See also 'head' and 'tail' if you only need one component.+--+-- If you are looking for @unsnoc@, you should use an 'RList', or a finite sequence/queue type.+uncons :: List a -> Maybe (a, List a)+{-# INLINABLE uncons #-}+uncons [] = Nothing+uncons (x:xs) = Just (x, xs)++-- | @O(1)@ extract the first element of a list, if it exists.+-- See also 'uncons' if you also need 'tail' at the same time.+head :: List a -> Maybe a+head Nil = Nothing+head (Cons x _) = Just x++-- | @O(1)@ extract the elements of a list other than the last, if they exist.+-- See also 'uncons' if you also need 'head' at the same time.+tail :: List a -> Maybe (List a)+tail Nil = Nothing+tail (Cons _ xs) = Just xs++-- | Create a single-element 'List'.+singleton :: a -> List a+{-# INLINE singleton #-}+singleton = (:[])
+ src/Data/List/Snoc.hs view
@@ -0,0 +1,203 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ViewPatterns #-}+++-- | Snoc-lists: lists where prepending is linear-time, but _appending_ is constant-time.+-- Useful for describing zippers and functional queues/buffers more naturally and safely.+--+-- We call it an `RList` because this is really just a vanilla list, but where+-- the semantics are that the last-added thing (internally cons'ed) is+-- understood to be at the \"end\" of the list.+--+-- WARNING: the `Foldable` instance provides a `Foldable.toList`; this simply unwraps the `RList` rather than reversing it.+-- If you need to convert from revered semantics to forward semantics, use this module's `toList`.+module Data.List.Snoc+  ( RList+  , Tsil+  -- * Introduction and Elimination+  , nil+  , snoc+  , singleton+  , unsnoc+  -- ** Patterns+  , pattern Nil+  , pattern Snoc+  -- * Queries+  , null+  , init+  , last+  -- * Traversal+  , catMaybes+  -- * Conversion+  , toList+  , fromList+  , reverseIn+  , reverseOut+  , toArrayN+  , toSet+  ) where++import Prelude hiding (null,init,last,reverse)++import Control.Applicative(Alternative(..))+import Control.DeepSeq (NFData)+import Data.Bifunctor (first)+import Data.Primitive.Contiguous (Contiguous, Element, SmallArray)+import Data.Set (Set)+import GHC.Generics (Generic)++import qualified Data.List as List+import qualified Data.Maybe as Maybe+import qualified Data.Primitive.Contiguous as Arr+import qualified Data.Set as Set+import qualified Prelude++-- | This datatype defines snoc-lists: lists with O(1) append and O(n) prepend.+-- Underneath the hood, it is just a plain list, but understood as containing its elements in reverse order.+newtype RList a = RList { unRList :: [a] }+  deriving stock (Generic)+  deriving newtype (Functor,Applicative)+instance (NFData a) => NFData (RList a)++instance (Show a) => Show (RList a) where+  show = show . toList+instance (Read a) => Read (RList a) where+  readsPrec i = (fmap . first) fromList . readsPrec i++instance Semigroup (RList a) where+  (RList a) <> (RList b) = RList (b <> a)+instance Monoid (RList a) where+  mempty = Nil++instance Alternative RList where+  empty = mempty+  (RList a) <|> (RList b) = RList (b <|> a)++-- | See? It's \"List\" in reverse?+-- I dunno, I just think 'RList' is an inelegant name, and word-initial @/t͜s/@ is one of my favorite phonemes.+type Tsil = RList++{-# COMPLETE Nil, Snoc #-}++-- | An empty 'RList', such as 'nil'.+pattern Nil :: RList a+pattern Nil = RList []++-- | The 'RList' consisting of initial and last elements, such as created by 'snoc'.+pattern Snoc :: RList a -> a -> RList a+pattern Snoc xs x <- (unsnoc -> Just (xs, x))+  where Snoc = snoc++-- | The empty 'RList'.+nil :: RList a+{-# INLINABLE nil #-}+nil = RList []++-- | @O(1)@ Append an element.+--+-- If you are looking for @cons@, you should use a plain list, or a finite sequence/queue type.+snoc :: RList a -> a -> RList a+{-# INLINABLE snoc #-}+snoc (RList xs) x = RList (x:xs)++-- | @O(1)@ Access the last element and initial portion of the list.+-- See also 'last' and 'init' if you only need one component.+--+-- If you are looking for @uncons@, you should use a plain list, or a finite sequence/queue type.+unsnoc :: RList a -> Maybe (RList a, a)+{-# INLINABLE unsnoc #-}+unsnoc (RList []) = Nothing+unsnoc (RList (x:xs)) = Just (RList xs, x)++-- | Create a single-element 'RList'.+singleton :: a -> RList a+{-# INLINE singleton #-}+singleton = RList . (:[])++-- | Test if an 'RList' is empty.+null :: RList a -> Bool+{-# INLINE null #-}+null (RList xs) = List.null xs++-- | @O(1)@ extract the last element of a list, if it exists.+-- See also 'unsnoc' if you also need 'init' at the same time.+last :: RList a -> Maybe a+last Nil = Nothing+last (Snoc _ x) = Just x++-- | @O(1)@ extract the elements of a list other than the last, if they exist.+-- See also 'unsnoc' if you also need 'last' at the same time.+init :: RList a -> Maybe (RList a)+init Nil = Nothing+init (Snoc xs _) = Just xs++-- | Remove all 'Nothing's from an 'RList' of 'Maybe's.+catMaybes :: RList (Maybe a) -> RList a+{-# INLINE catMaybes #-}+catMaybes = RList . Maybe.catMaybes . unRList++-- | @O(n)@ Convert to a plain list, maintaining order.+--+-- This is here so that you can escape back out to normal cons-list land once+-- you're done building your list.+--+-- See 'reverseOut' for when order doesn't matter.+toList :: RList a -> [a]+{-# INLINE toList #-}+toList (RList xs) = Prelude.reverse xs++-- | @O(n)@ Convert from a plain list, maintaining order.+--+-- This is added for completion's sake, as I'm not sure you'll often need this adapter.+--+-- See `toList` for the inverse, or `reverseIn` for when order doesn't matter.+fromList :: [a] -> RList a+{-# INLINE fromList #-}+fromList = RList . Prelude.reverse++-- | @O(0)@ Reverse an `RList`, returning a plain cons list.+--+-- This is here so that when the output list is fed to an order-agnostic+-- function, you don't have to pay the cost of reversing the representation.+--+-- See 'toList' for when order matters.+reverseOut :: RList a -> [a]+{-# INLINE reverseOut #-}+reverseOut = unRList+++-- | @O(0)@ Reverse a plain cons list, rerutning an `RList`.+--+-- See `reverseOut` for the inverse, and why you might use these.+reverseIn :: [a] -> RList a+{-# INLINE reverseIn #-}+reverseIn = RList++-- | Write the contents of the `RList` into an array, assuming you know the length of the array.+-- This is useful in the common case of buffering an unknown-length stream before allocating contiguous space for the elements.+--+-- If you sepcify to small a langth, the initial part of the array will be uninitialized.+-- If you specify to large a length, the initial part of the list will not be written.+--+-- If you are unaware of the size of the list, `Arr.fromList . fromList` will do the trick, but will obviously be slower.+toArrayN :: (Contiguous arr, Element arr a) => Int -> RList a -> arr a+{-# INLINE toArrayN #-} -- use inline instead of inlinable, because inlinable with Contiguous is busted+{-# SPECIALIZE toArrayN :: Int -> RList a -> SmallArray a #-}+toArrayN n (RList xs0) = Arr.create $ do+  mut <- Arr.new n+  loop mut (n - 1) xs0+  pure mut+  where+  loop _ (-1) _ = pure ()+  loop _ _ [] = pure ()+  loop arr i (x:xs) = do+    Arr.write arr i x+    loop arr (i - 1) xs++-- | Convert to a set without an intermediate conversion to a cons-list.+toSet :: (Ord a) => RList a -> Set a+{-# INLINABLE toSet #-}+toSet (RList xs) = Set.fromList xs