structs 0 → 0.1
raw patch · 15 files changed
+608/−227 lines, 15 filesdep +template-haskelldep ~basesetup-changednew-uploader
Dependencies added: template-haskell
Dependency ranges changed: base
Files
- CHANGELOG.markdown +10/−0
- HLint.hs +8/−0
- README.markdown +2/−2
- Setup.lhs +24/−36
- Warning.hs +5/−0
- src/Data/Struct.hs +1/−1
- src/Data/Struct/Internal.hs +95/−38
- src/Data/Struct/Internal/Label.hs +4/−2
- src/Data/Struct/Internal/LinkCut.hs +16/−28
- src/Data/Struct/Internal/Order.hs +31/−38
- src/Data/Struct/Order.hs +1/−0
- src/Data/Struct/TH.hs +370/−0
- structs.cabal +16/−4
- tests/doctests.hs +25/−0
- tests/doctests.hsc +0/−78
CHANGELOG.markdown view
@@ -1,3 +1,13 @@+## 0.1+* Add compare-and-swap support for struct slots+* Add `Data.Struct.TH`, which provides Template Haskell support for+ generating structs+* Remove unneeded proxy argument to `struct`+* Add a type parameter to `Order`+* Revamp `Setup.hs` to use `cabal-doctest`. This makes it build+ with `Cabal-2.0`, and makes the `doctest`s work with `cabal new-build` and+ sandboxes.+ ## 0 * Repository initialized * Added structures for list labeling, order-maintenance, and link-cut trees.
+ HLint.hs view
@@ -0,0 +1,8 @@+import "hint" HLint.HLint++ignore "Reduce duplication"+ignore "Redundant lambda"+ignore "Use >=>"+ignore "Use const"+ignore "Use module export list"+ignore "Use newtype instead of data"
README.markdown view
@@ -1,9 +1,9 @@ structs ========== -[](http://travis-ci.org/ekmett/structs)+[](https://hackage.haskell.org/package/structs) [](http://travis-ci.org/ekmett/structs) -This package explores strict mutable data structures in Haskell. +This package explores strict mutable data structures in Haskell. In particular, pointer-based data structures are effectively 'half price' due to the encoding used.
Setup.lhs view
@@ -1,46 +1,34 @@-#!/usr/bin/runhaskell \begin{code}+{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -Wall #-} module Main (main) where -import Data.List ( nub )-import Data.Version ( showVersion )-import Distribution.Package ( PackageName(PackageName), PackageId, InstalledPackageId, packageVersion, packageName )-import Distribution.PackageDescription ( PackageDescription(), TestSuite(..) )-import Distribution.Simple ( defaultMainWithHooks, UserHooks(..), simpleUserHooks )-import Distribution.Simple.Utils ( rewriteFile, createDirectoryIfMissingVerbose )-import Distribution.Simple.BuildPaths ( autogenModulesDir )-import Distribution.Simple.Setup ( BuildFlags(buildVerbosity), fromFlag)-import Distribution.Simple.LocalBuildInfo ( withLibLBI, withTestLBI, LocalBuildInfo(), ComponentLocalBuildInfo(componentPackageDeps) )-import Distribution.Verbosity ( Verbosity )-import System.FilePath ( (</>) )+#ifndef MIN_VERSION_cabal_doctest+#define MIN_VERSION_cabal_doctest(x,y,z) 0+#endif +#if MIN_VERSION_cabal_doctest(1,0,0)++import Distribution.Extra.Doctest ( defaultMainWithDoctests ) main :: IO ()-main = defaultMainWithHooks simpleUserHooks- { buildHook = \pkg lbi hooks flags -> do- generateBuildModule (fromFlag (buildVerbosity flags)) pkg lbi- buildHook simpleUserHooks pkg lbi hooks flags- , postHaddock = \args flags pkg lbi ->- postHaddock simpleUserHooks args flags pkg lbi- }+main = defaultMainWithDoctests "doctests" -generateBuildModule :: Verbosity -> PackageDescription -> LocalBuildInfo -> IO ()-generateBuildModule verbosity pkg lbi = do- let dir = autogenModulesDir lbi- createDirectoryIfMissingVerbose verbosity True dir- withLibLBI pkg lbi $ \_ libcfg -> do- withTestLBI pkg lbi $ \suite suitecfg -> do- rewriteFile (dir </> "Build_" ++ testName suite ++ ".hs") $ unlines- [ "module Build_" ++ testName suite ++ " where"- , "deps :: [String]"- , "deps = " ++ (show $ formatdeps (testDeps libcfg suitecfg))- ]- where- formatdeps = map (formatone . snd)- formatone p = case packageName p of- PackageName n -> n ++ "-" ++ showVersion (packageVersion p)+#else -testDeps :: ComponentLocalBuildInfo -> ComponentLocalBuildInfo -> [(InstalledPackageId, PackageId)]-testDeps xs ys = nub $ componentPackageDeps xs ++ componentPackageDeps ys+#ifdef MIN_VERSION_Cabal+-- If the macro is defined, we have new cabal-install,+-- but for some reason we don't have cabal-doctest in package-db+--+-- Probably we are running cabal sdist, when otherwise using new-build+-- workflow+import Warning ()+#endif++import Distribution.Simple++main :: IO ()+main = defaultMain++#endif \end{code}
+ Warning.hs view
@@ -0,0 +1,5 @@+module Warning+ {-# WARNING ["You are configuring this package without cabal-doctest installed.",+ "The doctests test-suite will not work as a result.",+ "To fix this, install cabal-doctest before configuring."] #-}+ () where
src/Data/Struct.hs view
@@ -28,7 +28,7 @@ , get, set , Field, field , unboxedField- , getField, setField+ , getField, setField, modifyField, modifyField' , Precomposable(..) ) where
src/Data/Struct/Internal.hs view
@@ -4,19 +4,17 @@ {-# LANGUAGE PolyKinds #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE RankNTypes #-}-{-# LANGUAGE MultiWayIf #-}-{-# LANGUAGE LambdaCase #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DefaultSignatures #-} {-# OPTIONS_HADDOCK not-home #-} ----------------------------------------------------------------------------- -- |--- Copyright : (C) 2015 Edward Kmett+-- Copyright : (C) 2015-2017 Edward Kmett -- License : BSD-style (see the file LICENSE) -- Maintainer : Edward Kmett <ekmett@gmail.com> -- Stability : experimental@@ -32,11 +30,15 @@ import Data.Primitive import Data.Coerce import GHC.Exts-import GHC.ST +-- $setup+-- >>> import Control.Monad.Primitive+ #ifdef HLINT {-# ANN module "HLint: ignore Eta reduce" #-} {-# ANN module "HLint: ignore Unused LANGUAGE pragma" #-}+{-# ANN module "HLint: ignore Avoid lambda" #-}+{-# ANN module "HLint: ignore Redundant lambda" #-} #endif data NullPointerException = NullPointerException deriving (Show, Exception)@@ -47,48 +49,51 @@ -- | Run an ST calculation inside of a PrimMonad. This lets us avoid dispatching everything through the 'PrimMonad' dictionary. st :: PrimMonad m => ST (PrimState m) a -> m a-st (ST f) = primitive f+st = primToPrim {-# INLINE[0] st #-} -{-# RULES "st/id" st = id #-}- -- | An instance for 'Struct' @t@ is a witness to the machine-level--- equivalence of @t@ and @Object@. The argument to 'struct' is--- ignored and is only present to help type inference.+-- equivalence of @t@ and @Object@. class Struct t where- struct :: proxy t -> Dict (Coercible (t s) (Object s))+ struct :: Dict (Coercible (t s) (Object s))+#ifndef HLINT+ default struct :: Coercible (t s) (Object s) => Dict (Coercible (t s) (Object s))+#endif+ struct = Dict+ {-# MINIMAL #-} data Object s = Object { runObject :: SmallMutableArray# s Any } -instance Struct Object where- struct _ = Dict+instance Struct Object --- TODO: get these to dispatch fast through 'coerce' using struct as a witness+coerceF :: Dict (Coercible a b) -> a -> b+coerceF Dict = coerce+{-# INLINE coerceF #-} +coerceB :: Dict (Coercible a b) -> b -> a+coerceB Dict = coerce+{-# INLINE coerceB #-}+ destruct :: Struct t => t s -> SmallMutableArray# s Any-destruct = unsafeCoerce# runObject+destruct = \x -> runObject (coerceF struct x) {-# INLINE destruct #-} construct :: Struct t => SmallMutableArray# s Any -> t s-construct = unsafeCoerce# Object+construct = \x -> coerceB struct (Object x) {-# INLINE construct #-} unsafeCoerceStruct :: (Struct x, Struct y) => x s -> y s-unsafeCoerceStruct x = unsafeCoerce# x+unsafeCoerceStruct x = construct (destruct x) eqStruct :: Struct t => t s -> t s -> Bool-eqStruct x y = isTrue# (destruct x `sameSmallMutableArray#` destruct y)+eqStruct = \x y -> isTrue# (destruct x `sameSmallMutableArray#` destruct y) {-# INLINE eqStruct #-} instance Eq (Object s) where (==) = eqStruct --- instance Struct Object s where--- construct = Object--- destruct = runObject- #ifndef HLINT-pattern Struct :: () => Struct t => SmallMutableArray# s Any -> t s+pattern Struct :: Struct t => () => SmallMutableArray# s Any -> t s pattern Struct x <- (destruct -> x) where Struct x = construct x #endif@@ -101,16 +106,27 @@ -- * Tony Hoare's billion dollar mistake -------------------------------------------------------------------------------- -data Box = Box !Null+-- | Box is designed to mirror object's single field but using the 'Null' type+-- instead of a mutable array. This hack relies on GHC reusing the same 'Null'+-- data constructor for all occurrences. Box's field must not be strict to+-- prevent the compiler from making assumptions about its contents.+data Box = Box Null data Null = Null +-- | Predicate to check if a struct is 'Nil'.+--+-- >>> isNil (Nil :: Object (PrimState IO))+-- True+-- >>> o <- alloc 1 :: IO (Object (PrimState IO))+-- >>> isNil o+-- False isNil :: Struct t => t s -> Bool isNil t = isTrue# (unsafeCoerce# reallyUnsafePtrEquality# (destruct t) Null) {-# INLINE isNil #-} #ifndef HLINT -- | Truly imperative.-pattern Nil :: forall t s. () => Struct t => t s+pattern Nil :: Struct t => () => t s pattern Nil <- (isNil -> True) where Nil = unsafeCoerce# Box Null #endif@@ -135,6 +151,10 @@ readMutableByteArraySmallArray# m i s = unsafeCoerce# readSmallArray# m i s {-# INLINE readMutableByteArraySmallArray# #-} +casSmallMutableArraySmallArray# :: SmallMutableArray# s Any -> Int# -> SmallMutableArray# s Any -> SmallMutableArray# s Any -> State# s -> (# State# s, Int#, SmallMutableArray# s Any #)+casSmallMutableArraySmallArray# m i o n s = unsafeCoerce# casSmallArray# m i o n s+{-# INLINE casSmallMutableArraySmallArray# #-}+ -------------------------------------------------------------------------------- -- * Field Accessors --------------------------------------------------------------------------------@@ -143,52 +163,60 @@ data Slot x y = Slot (forall s. SmallMutableArray# s Any -> State# s -> (# State# s, SmallMutableArray# s Any #)) (forall s. SmallMutableArray# s Any -> SmallMutableArray# s Any -> State# s -> State# s)+ (forall s. SmallMutableArray# s Any -> SmallMutableArray# s Any -> SmallMutableArray# s Any -> State# s -> (# State# s, Int#, SmallMutableArray# s Any #)) -- | We can compose slots to get a nested slot or field accessor class Precomposable t where ( # ) :: Slot x y -> t y z -> t x z instance Precomposable Slot where- Slot gxy _ # Slot gyz syz = Slot+ Slot gxy _ _ # Slot gyz syz cyz = Slot (\x s -> case gxy x s of (# s', y #) -> gyz y s') (\x z s -> case gxy x s of (# s', y #) -> syz y z s')+ (\x o n s -> case gxy x s of (# s', y #) -> cyz y o n s') -- | The 'Slot' at the given position in a 'Struct'-slot :: Int -> Slot s t+slot :: Int {- ^ slot -} -> Slot s t slot (I# i) = Slot (\m s -> readSmallMutableArraySmallArray# m i s) (\m a s -> writeSmallMutableArraySmallArray# m i a s)+ (\m o n s -> casSmallMutableArraySmallArray# m i o n s) -- | Get the value from a 'Slot' get :: (PrimMonad m, Struct x, Struct y) => Slot x y -> x (PrimState m) -> m (y (PrimState m))-get (Slot go _) x = primitive $ \s -> case go (destruct x) s of- (# s', y #) -> (# s', construct y #)+get (Slot go _ _) = \x -> primitive $ \s -> case go (destruct x) s of+ (# s', y #) -> (# s', construct y #) {-# INLINE get #-} -- | Set the value of a 'Slot' set :: (PrimMonad m, Struct x, Struct y) => Slot x y -> x (PrimState m) -> y (PrimState m) -> m ()-set (Slot _ go) x y = primitive_ (go (destruct x) (destruct y))+set (Slot _ go _) = \x y -> primitive_ (go (destruct x) (destruct y)) {-# INLINE set #-} +-- | Compare-and-swap the value of the slot. Takes the expected old value, the new value and returns if it succeeded and the value found.+cas :: (PrimMonad m, Struct x, Struct y) => Slot x y -> x (PrimState m) -> y (PrimState m) -> y (PrimState m) -> m (Bool, y (PrimState m))+cas (Slot _ _ go) = \m o n -> primitive $ \s -> case go (destruct m) (destruct o) (destruct n) s of+ (# s', i, r #) -> (# s', (tagToEnum# i :: Bool, construct r) #)+ -- | A 'Field' is a reference from a struct to a normal Haskell data type. data Field x a = Field- (forall s. SmallMutableArray# s Any -> State# s -> (# State# s, a #))- (forall s. SmallMutableArray# s Any -> a -> State# s -> State# s)+ (forall s. SmallMutableArray# s Any -> State# s -> (# State# s, a #)) -- get+ (forall s. SmallMutableArray# s Any -> a -> State# s -> State# s) -- set instance Precomposable Field where- Slot gxy _ # Field gyz syz = Field+ Slot gxy _ _ # Field gyz syz = Field (\x s -> case gxy x s of (# s', y #) -> gyz y s') (\x z s -> case gxy x s of (# s', y #) -> syz y z s') -- | Store the reference to the Haskell data type in a normal field-field :: Int -> Field s a+field :: Int {- ^ slot -} -> Field s a field (I# i) = Field (\m s -> unsafeCoerce# readSmallArray# m i s) (\m a s -> unsafeCoerce# writeSmallArray# m i a s) {-# INLINE field #-} --- | Store the reference in the nth slot of the nth argument, treated as a MutableByteArray-unboxedField :: Prim a => Int -> Int -> Field s a+-- | Store the reference in the nth slot in the nth argument, treated as a MutableByteArray+unboxedField :: Prim a => Int {- ^ slot -} -> Int {- ^ argument -} -> Field s a unboxedField (I# i) (I# j) = Field (\m s -> case readMutableByteArraySmallArray# m i s of (# s', mba #) -> readByteArray# mba j s')@@ -196,12 +224,41 @@ (# s', mba #) -> writeByteArray# mba j a s') {-# INLINE unboxedField #-} +-- | Initialized the mutable array used by 'unboxedField'. Returns the array+-- after storing it in the struct to help with initialization.+initializeUnboxedField ::+ (PrimMonad m, Struct x) =>+ Int {- ^ slot -} ->+ Int {- ^ elements -} ->+ Int {- ^ element size -} ->+ x (PrimState m) {- ^ struct -} ->+ m (MutableByteArray (PrimState m))+initializeUnboxedField (I# i) (I# n) (I# z) m =+ primitive $ \s ->+ case newByteArray# (n *# z) s of+ (# s1, mba #) ->+ (# writeMutableByteArraySmallArray# (destruct m) i mba s1, MutableByteArray mba #)+{-# INLINE initializeUnboxedField #-}+ -- | Get the value of a field in a struct getField :: (PrimMonad m, Struct x) => Field x a -> x (PrimState m) -> m a-getField (Field go _) x = primitive (go (destruct x))+getField (Field go _) = \x -> primitive (go (destruct x)) {-# INLINE getField #-} -- | Set the value of a field in a struct setField :: (PrimMonad m, Struct x) => Field x a -> x (PrimState m) -> a -> m ()-setField (Field _ go) x y = primitive_ (go (destruct x) y)+setField (Field _ go) = \x y -> primitive_ (go (destruct x) y) {-# INLINE setField #-}+++--------------------------------------------------------------------------------+-- * Modifiers+--------------------------------------------------------------------------------++modifyField :: (Struct x, PrimMonad m) => Field x a -> x (PrimState m) -> (a -> a) -> m ()+modifyField s = \o f -> st (setField s o . f =<< getField s o)+{-# INLINE modifyField #-}++modifyField' :: (Struct x, PrimMonad m) => Field x a -> x (PrimState m) -> (a -> a) -> m ()+modifyField' s = \o f -> st (setField s o =<< (\x -> return $! f x) =<< getField s o)+{-# INLINE modifyField' #-}
src/Data/Struct/Internal/Label.hs view
@@ -21,6 +21,9 @@ import Data.Struct.Internal import Data.Word +-- $setup+-- >>> import Data.Struct.Internal.Label+ #ifdef HLINT {-# ANN module "HLint: ignore Eta reduce" #-} #endif@@ -54,8 +57,7 @@ instance Eq (Label s) where (==) = eqStruct -instance Struct Label where- struct _ = Dict+instance Struct Label -- | Construct an explicit list labeling structure. --
src/Data/Struct/Internal/LinkCut.hs view
@@ -1,8 +1,11 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_HADDOCK not-home #-}+{-# OPTIONS_GHC -fno-warn-monomorphism-restriction #-} ----------------------------------------------------------------------------- -- |--- Copyright : (C) 2015 Edward Kmett+-- Copyright : (C) 2015-2017 Edward Kmett -- License : BSD-style (see the file LICENSE) -- Maintainer : Edward Kmett <ekmett@gmail.com> -- Stability : experimental@@ -17,7 +20,11 @@ import Control.Monad.Primitive import Control.Monad.ST import Data.Struct.Internal+import Data.Struct.TH +-- $setup+-- >>> import Data.Struct.Internal.LinkCut+ #ifdef HLINT {-# ANN module "HLint: ignore Reduce duplication" #-} {-# ANN module "HLint: ignore Redundant do" #-}@@ -73,35 +80,16 @@ -- "zwu" -- >>> (z ==) <$> root v -- True-newtype LinkCut a s = LinkCut (Object s)--instance Struct (LinkCut a) where- struct _ = Dict--instance Eq (LinkCut a s) where- (==) = eqStruct--path, parent, left, right :: Slot (LinkCut a) (LinkCut a)-path = slot 0-parent = slot 1-left = slot 2-right = slot 3--value, summary :: Field (LinkCut a) a-value = field 4-summary = field 5+makeStruct [d|+ data LinkCut a s = LinkCut+ { path, parent, left, right :: !(LinkCut a s)+ , value, summary :: a+ }+ |] -- | O(1). Allocate a new link-cut tree with a given monoidal summary.-new :: (PrimMonad m, Monoid a) => a -> m (LinkCut a (PrimState m))-new a = st $ do- this <- alloc 6- set path this Nil- set parent this Nil- set left this Nil- set right this Nil- setField value this a- setField summary this a- return this+new :: PrimMonad m => a -> m (LinkCut a (PrimState m))+new a = st (newLinkCut Nil Nil Nil Nil a a) {-# INLINE new #-} -- | O(log n). @'cut' v@ removes the linkage between @v@ upwards to whatever tree it was in, making @v@ a root node.
src/Data/Struct/Internal/Order.hs view
@@ -38,41 +38,45 @@ -- -- This means that inserts are O(1) amortized, while comparisons remain O(1) worst-case. -newtype Order s = Order { runOrder :: Object s }+newtype Order a s = Order { runOrder :: Object s } -instance Eq (Order s) where (==) = eqStruct+instance Eq (Order a s) where (==) = eqStruct -instance Struct Order where- struct _ = Dict+instance Struct (Order a) -key :: Field Order Key+key :: Field (Order a) Key key = field 0 {-# INLINE key #-} -next :: Slot Order Order-next = slot 1+value :: Field (Order a) a+value = field 1+{-# INLINE value #-}++next :: Slot (Order a) (Order a)+next = slot 2 {-# INLINE next #-} -prev :: Slot Order Order-prev = slot 2+prev :: Slot (Order a) (Order a)+prev = slot 3 {-# INLINE prev #-} -parent :: Slot Order Label-parent = slot 3+parent :: Slot (Order a) Label +parent = slot 4 {-# INLINE parent #-} -makeOrder :: PrimMonad m => Label (PrimState m) -> Key -> Order (PrimState m) -> Order (PrimState m) -> m (Order (PrimState m))-makeOrder mom a p n = st $ do- this <- alloc 4+makeOrder :: PrimMonad m => Label (PrimState m) -> Key -> a -> Order a (PrimState m) -> Order a (PrimState m) -> m (Order a (PrimState m))+makeOrder mom a v p n = st $ do+ this <- alloc 5 set parent this mom setField key this a+ setField value this v set prev this p set next this n return this {-# INLINE makeOrder #-} -- | O(1) compareM, O(1) amortized insert-compareM :: PrimMonad m => Order (PrimState m) -> Order (PrimState m) -> m Ordering+compareM :: PrimMonad m => Order a (PrimState m) -> Order a (PrimState m) -> m Ordering compareM i j | isNil i || isNil j = throw NullPointerException | otherwise = st $ do@@ -84,22 +88,22 @@ x -> return x {-# INLINE compareM #-} -insertAfter :: PrimMonad m => Order (PrimState m) -> m (Order (PrimState m))-insertAfter n0 = st $ do+insertAfter :: PrimMonad m => Order a (PrimState m) -> a -> m (Order a (PrimState m))+insertAfter n0 a1 = st $ do when (isNil n0) $ throw NullPointerException mom <- get parent n0 k0 <- getField key n0 n2 <- get next n0 k2 <- if isNil n2 then return maxBound else getField key n2 let !k1 = k0 + unsafeShiftR (k2 - k0) 1- n1 <- makeOrder mom k1 n0 n2+ n1 <- makeOrder mom k1 a1 n0 n2 unless (isNil n2) $ set prev n2 n1 set next n0 n1 when (k0 + 1 == k2) $ rewind mom n0 -- we have a collision, rebalance return n1 where -- find the smallest sibling- rewind :: Label s -> Order s -> ST s ()+ rewind :: Label s -> Order a s -> ST s () rewind mom this = do p <- get prev this if isNil p then rebalance mom mom this 0 64@@ -109,7 +113,7 @@ else rebalance mom mom p 0 64 -- break up the family- rebalance :: Label s -> Label s -> Order s -> Word64 -> Int -> ST s ()+ rebalance :: Label s -> Label s -> Order a s -> Word64 -> Int -> ST s () rebalance mom dad this k j = unless (isNil this) $ do guardian <- get parent this when (mom == guardian) $ do@@ -121,7 +125,7 @@ stepdad <- Label.insertAfter dad rebalance mom stepdad n deltaU logU -delete :: PrimMonad m => Order (PrimState m) -> m ()+delete :: PrimMonad m => Order a (PrimState m) -> m () delete this = st $ do when (isNil this) $ throw NullPointerException mom <- get parent this@@ -156,25 +160,14 @@ deltaU :: Key deltaU = unsafeShiftR maxBound loglogU -- U / log U -new :: PrimMonad m => m (Order (PrimState m))-new = st $ do+new :: PrimMonad m => a -> m (Order a (PrimState m))+new a = st $ do l <- Label.new- makeOrder l (unsafeShiftR maxBound 1) Nil Nil+ makeOrder l (unsafeShiftR maxBound 1) a Nil Nil {-# INLINE new #-} -value :: PrimMonad m => Order (PrimState m) -> m (Key, Key)-value this = st $ do+keys :: PrimMonad m => Order a (PrimState m) -> m (Key, Key)+keys this = st $ do mom <- get parent this (,) <$> getField Label.key mom <*> getField key this-{-# INLINE value #-}---- O(n) sweep to the right from the current node, showing all values for debugging-values :: PrimMonad m => Order (PrimState m) -> m [(Key, Key)]-values xs0 = st (go xs0) where- go :: Order s -> ST s [(Key, Key)]- go this- | isNil this = return []- | otherwise = do- n <- get next this- (:) <$> value this <*> go n-{-# INLINE values #-}+{-# INLINE keys #-}
src/Data/Struct/Order.hs view
@@ -11,6 +11,7 @@ module Data.Struct.Order ( Order , new+ , value , insertAfter , delete ) where
+ src/Data/Struct/TH.hs view
@@ -0,0 +1,370 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskell #-}++module Data.Struct.TH (makeStruct) where++import Control.Monad (when, zipWithM)+import Control.Monad.Primitive (PrimMonad, PrimState)+import Data.Either (partitionEithers)+import Data.Primitive+import Data.Struct+import Data.Struct.Internal (Dict(Dict), initializeUnboxedField, st)+import Data.List (groupBy, nub)+import Language.Haskell.TH+import Language.Haskell.TH.Syntax (VarStrictType)++#ifdef HLINT+{-# ANN module "HLint: ignore Use ." #-}+#endif++data StructRep = StructRep+ { srState :: Name+ , srName :: Name+ , srTyVars :: [TyVarBndr]+#if MIN_VERSION_template_haskell(2,12,0)+ , srDerived :: [DerivClause]+#else+ , srDerived :: Cxt+#endif+ , srCxt :: Cxt+ , srConstructor :: Name+ , srMembers :: [Member]+ } deriving Show++data Member = Member+ { _memberRep :: Representation+ , memberName :: Name+ , _memberType :: Type+ }+ deriving Show++data Representation = BoxedField | UnboxedField | Slot+ deriving Show++-- | Generate allocators, slots, fields, unboxed fields, Eq instances,+-- and Struct instances for the given "data types".+--+-- Inputs are expected to be "data types" parameterized by a state+-- type. Strict fields are considered to be slots, Non-strict fields+-- are considered to be boxed types, Unpacked fields are considered+-- to be unboxed primitives.+--+-- The data type should use record syntax and have a single constructor.+-- The field names will be used to generate slot, field, and unboxedField+-- values of the same name.+--+-- An allocator for the struct is generated by prefixing "alloc" to the+-- data type name.+makeStruct :: DecsQ -> DecsQ+makeStruct dsq =+ do ds <- dsq+ (passthrough, reps) <- partitionEithers <$> traverse computeRep ds+ ds's <- traverse (generateCode passthrough) reps+ return (passthrough ++ concat ds's)++mkAllocName :: StructRep -> Name+mkAllocName rep = mkName ("alloc" ++ nameBase (srName rep))++mkInitName :: StructRep -> Name+mkInitName rep = mkName ("new" ++ nameBase (srName rep))++------------------------------------------------------------------------+-- Input validation+------------------------------------------------------------------------++computeRep :: Dec -> Q (Either Dec StructRep)+computeRep (DataD c n vs _ cs ds) =+ do state <- validateStateType vs+ (conname, confields) <- validateContructor cs+ members <- traverse (validateMember state) confields++ return $ Right StructRep+ { srState = state+ , srName = n+ , srTyVars = vs+ , srConstructor = conname+ , srMembers = members+ , srDerived = ds+ , srCxt = c+ }+computeRep d = return (Left d)++-- | Check that only a single data constructor was provided and+-- that it was a record constructor.+validateContructor :: [Con] -> Q (Name,[VarStrictType])+validateContructor [RecC name fields] = return (name,fields)+validateContructor [_] = fail "Expected a record constructor"+validateContructor xs = fail ("Expected 1 constructor, got " ++ show (length xs))++-- A struct type's final type variable should be suitable for+-- use as the ('PrimState' m) argument.+validateStateType :: [TyVarBndr] -> Q Name+validateStateType xs =+ do when (null xs) (fail "state type expected but no type variables found")+ case last xs of+ PlainTV n -> return n+ KindedTV n k+ | k == starK -> return n+ | otherwise -> fail "state type should have kind *"+++-- | Figure out which record fields are Slots and which are+-- Fields. Slots will have types ending in the state type+validateMember :: Name -> VarStrictType -> Q Member+validateMember s (fieldname,Bang NoSourceUnpackedness NoSourceStrictness,fieldtype) =+ do when (occurs s fieldtype)+ (fail ("state type may not occur in field `" ++ nameBase fieldname ++ "`"))+ return (Member BoxedField fieldname fieldtype)+validateMember s (fieldname,Bang NoSourceUnpackedness SourceStrict,fieldtype) =+ do f <- unapplyType fieldtype s+ when (occurs s f)+ (fail ("state type may only occur in final position in slot `" ++ nameBase fieldname ++ "`"))+ return (Member Slot fieldname f)+validateMember s (fieldname,Bang SourceUnpack SourceStrict,fieldtype) =+ do when (occurs s fieldtype)+ (fail ("state type may not occur in unpacked field `" ++ nameBase fieldname ++ "`"))+ return (Member UnboxedField fieldname fieldtype)+validateMember _ _ = fail "validateMember: can't unpack nonstrict fields"++unapplyType :: Type -> Name -> Q Type+unapplyType (AppT f (VarT x)) y | x == y = return f+unapplyType _ _ = fail "Unable to match state type of slot"++------------------------------------------------------------------------+-- Code generation+------------------------------------------------------------------------++generateCode :: [Dec] -> StructRep -> DecsQ+generateCode ds rep = concat <$> sequence+ [ generateDataType rep+ , generateStructInstance rep+ , generateMembers rep+ , generateNew rep+ , generateAlloc rep+ , generateRoles ds rep+ ]++-- Generates: newtype TyCon a b c s = DataCon (Object s)+generateDataType :: StructRep -> DecsQ+generateDataType rep = sequence+ [ newtypeD (return (srCxt rep)) (srName rep) (srTyVars rep)+ Nothing+ (normalC+ (srConstructor rep)+ [ bangType+ (bang noSourceUnpackedness noSourceStrictness)+ [t| Object $(varT (srState rep)) |]+ ])+#if MIN_VERSION_template_haskell(2,12,0)+ (map return (srDerived rep))+#else+ (return (srDerived rep))+#endif+ ]++generateRoles :: [Dec] -> StructRep -> DecsQ+generateRoles ds rep+ | hasRoleAnnotation = return []+ | otherwise = sequence [ roleAnnotD (srName rep) (computeRoles rep) ]++ where+ hasRoleAnnotation = any isTargetRoleAnnot ds++ isTargetRoleAnnot (RoleAnnotD n _) = n == srName rep+ isTargetRoleAnnot _ = False++-- Currently all roles are set to nominal. A more general solution+-- should be able to infer some representional/phantom roles. To do+-- this for arbitrary types we'll need a way to query the roles of+-- existing type constructors to infer the correct roles.+computeRoles :: StructRep -> [Role]+computeRoles = map (const NominalR) . srTyVars++-- | Type of the object not applied to a state type. This+-- should have kind * -> *+repType1 :: StructRep -> TypeQ+repType1 rep = repTypeHelper (srName rep) (init (srTyVars rep))++-- | Type of the object as originally declared, fully applied.+repType :: StructRep -> TypeQ+repType rep = repTypeHelper (srName rep) (srTyVars rep)++repTypeHelper :: Name -> [TyVarBndr] -> TypeQ+repTypeHelper c vs = foldl appT (conT c) (tyVarBndrT <$> vs)++-- Construct a 'TypeQ' from a 'TyVarBndr'+tyVarBndrT :: TyVarBndr -> TypeQ+tyVarBndrT (PlainTV n ) = varT n+tyVarBndrT (KindedTV n k) = sigT (varT n) k++generateStructInstance :: StructRep -> DecsQ+generateStructInstance rep =+ [d| instance Struct $(repType1 rep) where struct = Dict+ instance Eq $(repType rep) where (==) = eqStruct+ |]++-- generates: allocDataCon = alloc <n>+generateAlloc :: StructRep -> DecsQ+generateAlloc rep =+ do mName <- newName "m"+ let m = varT mName+ n = length (groupBy isNeighbor (srMembers rep))+ allocName = mkAllocName rep++ simpleDefinition rep allocName+ (forallT [PlainTV mName] (cxt [])+ [t| PrimMonad $m => $m ( $(repType1 rep) (PrimState $m) ) |])+ [| alloc n |]+++-- generates:+-- newDataCon a .. = do this <- alloc <n>; set field1 this a; ...; return this+generateNew :: StructRep -> DecsQ+generateNew rep =+ do this <- newName "this"+ let ms = groupBy isNeighbor (srMembers rep)++ addName m = do n <- newName (nameBase (memberName m))+ return (n,m)++ msWithArgs <- traverse (traverse addName) ms++ let name = mkInitName rep+ body = doE+ -- allocate struct+ $ bindS (varP this) (varE (mkAllocName rep))++ -- initialize each member+ : (noBindS <$> zipWith (assignN (varE this)) [0..] msWithArgs)++ -- return initialized struct+ ++ [ noBindS [| return $(varE this) |] ]++ sequence+ [ sigD name (newStructType rep)+ , funD name [ clause (varP . fst <$> concat msWithArgs)+ (normalB [| st $body |] ) [] ]+ ]+++assignN :: ExpQ -> Int -> [(Name,Member)] -> ExpQ++assignN this _ [(arg,Member BoxedField n _)] =+ [| setField $(varE n) $this $(varE arg) |]++assignN this _ [(arg,Member Slot n _)] =+ [| set $(varE n) $this $(varE arg)|]++assignN this i us =+ do let n = length us+ mba <- newName "mba"+ let arg0 = fst (head us)+ doE $ bindS (varP mba) [| initializeUnboxedField i n (sizeOf $(varE arg0)) $this |]+ : [ noBindS [| writeByteArray $(varE mba) j $(varE arg) |]+ | (j,(arg,_)) <- zip [0 :: Int ..] us ]++-- | The type of the struct initializer is complicated enough to+-- pull it out here.+-- generates:+-- PrimMonad m => field1 -> field2 -> ... -> m (TyName a b ... (PrimState m))+newStructType :: StructRep -> TypeQ+newStructType rep =+ do mName <- newName "m"+ let m = varT mName+ s = [t| PrimState $m |]+ obj = repType1 rep++ buildType (Member BoxedField _ t) = return t+ buildType (Member UnboxedField _ t) = return t+ buildType (Member Slot _ f) = [t| $(return f) $s |]++ r = foldr (-->)+ [t| $m ($obj $s) |]+ (buildType <$> srMembers rep)++ primPreds = primPred <$> nub [ t | Member UnboxedField _ (VarT t) <- srMembers rep ]++ forallRepT rep $ forallT [PlainTV mName] (cxt primPreds)+ [t| PrimMonad $m => $r |]++-- generates a slot, field, or unboxedField definition per member+generateMembers :: StructRep -> DecsQ+generateMembers rep+ = concat <$>+ zipWithM+ (generateMember1 rep)+ [0..]+ (groupBy isNeighbor (srMembers rep))++isNeighbor :: Member -> Member -> Bool+isNeighbor (Member UnboxedField _ t) (Member UnboxedField _ u) = t == u+isNeighbor _ _ = False++------------------------------------------------------------------------++generateMember1 :: StructRep -> Int -> [Member] -> DecsQ++-- generates: fieldname = field <n>+generateMember1 rep n [Member BoxedField fieldname fieldtype] =+ simpleDefinition rep fieldname+ [t| Field $(repType1 rep) $(return fieldtype) |]+ [| field n |]++-- generates: slotname = slot <n>+generateMember1 rep n [Member Slot slotname slottype] =+ simpleDefinition rep slotname+ [t| Slot $(repType1 rep) $(return slottype) |]+ [| slot n |]++-- It the first type patterns didn't hit then we expect a list+-- of unboxed fields due to the call to groupBy in generateMembers+-- generates: fieldname = unboxedField <n> <i>+generateMember1 rep n us =+ concat <$> sequence+ [ simpleDefinition rep fieldname+ (addPrimCxt fieldtype+ [t| Field $(repType1 rep) $(return fieldtype) |])+ [| unboxedField n i |]++ | (i,Member UnboxedField fieldname fieldtype) <- zip [0 :: Int ..] us+ ]+ where+ addPrimCxt (VarT t) = forallT [] (cxt [primPred t])+ addPrimCxt _ = id++-- Generate code for definitions without arguments, with type variables+-- quantified over those in the struct rep, including an inline pragma+simpleDefinition :: StructRep -> Name -> TypeQ -> ExpQ -> DecsQ+simpleDefinition rep name typ def =+ sequence+ [ sigD name (forallRepT rep typ)+ , simpleValD name def+ , pragInlD name Inline FunLike AllPhases+ ]++------------------------------------------------------------------------++-- Simple use of 'valD' bind an expression to a name+simpleValD :: Name -> ExpQ -> DecQ+simpleValD var val = valD (varP var) (normalB val) []++-- Quantifies over all of the type variables in a struct data type+-- except the state variable which is likely to be ('PrimState' s)+forallRepT :: StructRep -> TypeQ -> TypeQ+forallRepT rep = forallT (init (srTyVars rep)) (cxt [])++(-->) :: TypeQ -> TypeQ -> TypeQ+f --> x = arrowT `appT` f `appT` x++primPred :: Name -> PredQ+primPred t = [t| Prim $(varT t) |]++occurs :: Name -> Type -> Bool+occurs n (AppT f x) = occurs n f || occurs n x+occurs n (VarT m) = n == m+occurs n (ForallT _ _ t) = occurs n t -- all names are fresh in quoted code, see below+occurs n (SigT t _) = occurs n t+occurs _ _ = False++-- Prelude Language.Haskell.TH> runQ (stringE . show =<< [t| forall a. a -> (forall a. a) |])+-- LitE (StringL "ForallT [PlainTV a_0] [] (AppT (AppT ArrowT (VarT a_0)) (ForallT [PlainTV a_1] [] (VarT a_1)))")
structs.cabal view
@@ -1,6 +1,6 @@ name: structs category: Data-version: 0+version: 0.1 license: BSD3 cabal-version: >= 1.22 license-file: LICENSE@@ -9,17 +9,25 @@ stability: provisional homepage: http://github.com/ekmett/structs/ bug-reports: http://github.com/ekmett/structs/issues-copyright: Copyright (C) 2015 Edward A. Kmett+copyright: Copyright (C) 2015-2017 Edward A. Kmett build-type: Custom-tested-with: GHC == 7.10.1, GHC == 7.10.2+tested-with: GHC == 8.0.2, GHC == 8.2.1 synopsis: Strict GC'd imperative object-oriented programming with cheap pointers. description: This project is an experiment with a small GC'd strict mutable imperative universe with cheap pointers inside of the GHC runtime system. extra-source-files: CHANGELOG.markdown+ HLint.hs README.markdown+ Warning.hs +custom-setup+ setup-depends:+ base >= 4.9 && <5,+ Cabal >= 1.10,+ cabal-doctest >= 1.0.1 && <1.1+ source-repository head type: git location: git://github.com/ekmett/structs.git@@ -36,13 +44,15 @@ library build-depends:- base >= 4.8 && < 5,+ base >= 4.9 && < 5, deepseq,+ template-haskell >= 2.11 && < 2.13, ghc-prim, primitive exposed-modules: Data.Struct+ Data.Struct.TH Data.Struct.Internal Data.Struct.Internal.Label Data.Struct.Internal.LinkCut@@ -61,6 +71,8 @@ ghc-options: -Wall -threaded hs-source-dirs: tests default-language: Haskell2010++ x-doctest-options: -fobject-code if !flag(test-doctests) buildable: False
+ tests/doctests.hs view
@@ -0,0 +1,25 @@+-----------------------------------------------------------------------------+-- |+-- Module : Main (doctests)+-- Copyright : (C) 2012-14 Edward Kmett+-- License : BSD-style (see the file LICENSE)+-- Maintainer : Edward Kmett <ekmett@gmail.com>+-- Stability : provisional+-- Portability : portable+--+-- This module provides doctests for a project based on the actual versions+-- of the packages it was built with. It requires a corresponding Setup.lhs+-- to be added to the project+-----------------------------------------------------------------------------+module Main where++import Build_doctests (flags, pkgs, module_sources)+import Data.Foldable (traverse_)+import Test.DocTest++main :: IO ()+main = do+ traverse_ putStrLn args+ doctest args+ where+ args = flags ++ pkgs ++ module_sources
− tests/doctests.hsc
@@ -1,78 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ForeignFunctionInterface #-}--------------------------------------------------------------------------------- |--- Module : Main (doctests)--- Copyright : (C) 2012-14 Edward Kmett--- License : BSD-style (see the file LICENSE)--- Maintainer : Edward Kmett <ekmett@gmail.com>--- Stability : provisional--- Portability : portable------ This module provides doctests for a project based on the actual versions--- of the packages it was built with. It requires a corresponding Setup.lhs--- to be added to the project-------------------------------------------------------------------------------module Main where--import Build_doctests (deps)-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative-#endif-import Control.Monad-import Data.List-import System.Directory-import System.FilePath-import Test.DocTest--##if defined(mingw32_HOST_OS)-##if defined(i386_HOST_ARCH)-##define USE_CP-import Control.Applicative-import Control.Exception-import Foreign.C.Types-foreign import stdcall "windows.h SetConsoleCP" c_SetConsoleCP :: CUInt -> IO Bool-foreign import stdcall "windows.h GetConsoleCP" c_GetConsoleCP :: IO CUInt-##elif defined(x86_64_HOST_ARCH)-##define USE_CP-import Control.Applicative-import Control.Exception-import Foreign.C.Types-foreign import ccall "windows.h SetConsoleCP" c_SetConsoleCP :: CUInt -> IO Bool-foreign import ccall "windows.h GetConsoleCP" c_GetConsoleCP :: IO CUInt-##endif-##endif---- | Run in a modified codepage where we can print UTF-8 values on Windows.-withUnicode :: IO a -> IO a-##ifdef USE_CP-withUnicode m = do- cp <- c_GetConsoleCP- (c_SetConsoleCP 65001 >> m) `finally` c_SetConsoleCP cp-##else-withUnicode m = m-##endif--main :: IO ()-main = withUnicode $ getSources >>= \sources -> doctest $- "-isrc"- : "-idist/build/autogen"- : "-optP-include"- : "-optPdist/build/autogen/cabal_macros.h"- : "-hide-all-packages"-#ifdef TRUSTWORTHY- : "-DTRUSTWORTHY=1"-#endif- : map ("-package="++) deps ++ sources--getSources :: IO [FilePath]-getSources = filter (isSuffixOf ".hs") <$> go "src"- where- go dir = do- (dirs, files) <- getFilesAndDirectories dir- (files ++) . concat <$> mapM go dirs--getFilesAndDirectories :: FilePath -> IO ([FilePath], [FilePath])-getFilesAndDirectories dir = do- c <- map (dir </>) . filter (`notElem` ["..", "."]) <$> getDirectoryContents dir- (,) <$> filterM doesDirectoryExist c <*> filterM doesFileExist c