packages feed

storable-record 0.0.1 → 0.0.2

raw patch · 5 files changed

+327/−58 lines, 5 filesdep ~base

Dependency ranges changed: base

Files

src/Foreign/Storable/Newtype.hs view
@@ -1,5 +1,17 @@ {- | Storable instances for simple wrapped types.++Example:++> import Foreign.Storable.Newtype as Store+>+> newtype MuLaw = MuLaw {deMuLaw :: Word8}+>+> instance Storable MuLaw where+>    sizeOf = Store.sizeOf deMuLaw+>    alignment = Store.alignment deMuLaw+>    peek = Store.peek MuLaw+>    poke = Store.poke deMuLaw -} module Foreign.Storable.Newtype where 
src/Foreign/Storable/Record.hs view
@@ -1,3 +1,56 @@+{- |+Here we show an example of how to+define a Storable instance with this module.++> import Foreign.Storable.Record as Store+> import Foreign.Storable (Storable (..), )+>+> import Control.Applicative (liftA2, )+>+> data Stereo a = Stereo {left, right :: a}+>+> store :: Storable a => Store.Dictionary (Stereo a)+> store =+>    Store.run $+>    liftA2 Stereo+>       (Store.element left)+>       (Store.element right)+>+> instance (Storable a) => Storable (Stereo a) where+>    sizeOf = Store.sizeOf store+>    alignment = Store.alignment store+>    peek = Store.peek store+>    poke = Store.poke store+++The @Stereo@ constructor is exclusively used+for constructing the @peek@ function,+whereas the accessors in the @element@ calls+are used for assembling the @poke@ function.+It is required that the order of arguments of @Stereo@+matches the record accessors in the @element@ calls.+If you want that the stored data correctly and fully represents+your Haskell data, it must hold:++>   Stereo (left x) (right x) = x   .++Unfortunately this cannot be checked automatically.+However, mismatching types that are caused by swapped arguments+are detected by the type system.+Our system performs for you:+Size and alignment computation, poking and peeking.+Thus several inconsistency bugs can be prevented using this package,+like size mismatching the space required by @poke@ actions.+There is no more restriction,+thus smart constructors and accessors+and nested records work, too.+For nested records however,+I recommend individual Storable instances for the sub-records.++You see it would simplify class instantiation+if we could tell the class dictionary at once+instead of defining each method separately.+-} module Foreign.Storable.Record (    Dictionary, Access,    element, run,@@ -99,8 +152,8 @@ {-# INLINE element #-} element :: Storable a => (r -> a) -> Access r a element f =-   let align = St.alignment (f undefined)-       size  = St.sizeOf (f undefined)+   let align = St.alignment (f (error "Storable.Record.element.alignment: content touched"))+       size  = St.sizeOf (f (error "Storable.Record.element.size: content touched"))    in  Access $        Compose $ writer $ flip (,) (Alignment align) $        Compose $
+ src/Foreign/Storable/Traversable.hs view
@@ -0,0 +1,130 @@+{- |+If you have a 'Trav.Traversable' instance of a record,+you can load and store all elements,+that are accessible by @Traversable@ methods.+We treat the record like an array,+that is we assume, that all elements have the same size and alignment.++Example:++> import Foreign.Storable.Traversable as Store+>+> data Stereo a = Stereo {left, right :: a}+>+> instance Functor Stereo where+>    fmap = Trav.fmapDefault+>+> instance Foldable Stereo where+>    foldMap = Trav.foldMapDefault+>+> instance Traversable Stereo where+>    sequenceA ~(Stereo l r) = liftA2 Stereo l r+>+> instance (Storable a) => Storable (Stereo a) where+>    sizeOf = Store.sizeOf+>    alignment = Store.alignment+>    peek = Store.peek (error "instance Traversable Stereo is lazy, so we do not provide a real value here")+>    poke = Store.poke++You would certainly not define the 'Trav.Traversable' and according instances+just for the implementation of the 'Storable' instance,+but there are usually similar applications+where the @Traversable@ instance is useful.+-}+module Foreign.Storable.Traversable (+   alignment, sizeOf,+   peek, poke,+   peekApplicative,+   ) where++import qualified Data.Traversable as Trav+import qualified Data.Foldable as Fold+import qualified Control.Applicative as App++import Control.Monad.Trans.State+          (StateT, evalStateT, get, put, modify, )+import Control.Monad.Trans (liftIO, )++import Foreign.Storable.FixedArray (roundUp, )+import qualified Foreign.Storable as St++import Foreign.Ptr (Ptr, castPtr, )+import Foreign.Storable (Storable, )+import Foreign.Marshal.Array (advancePtr, )+++{-# INLINE elementType #-}+elementType :: f a -> a+elementType _ =+   error "Storable.Traversable.alignment and sizeOf may not depend on element values"++{-# INLINE alignment #-}+alignment ::+   (Fold.Foldable f, Storable a) =>+   f a -> Int+alignment = St.alignment . elementType++{-# INLINE sizeOf #-}+sizeOf ::+   (Fold.Foldable f, Storable a) =>+   f a -> Int+sizeOf f =+   Fold.foldl' (\s _ -> s + 1) 0 f *+   roundUp (alignment f) (St.sizeOf (elementType f))+++{- |+@peek skeleton ptr@ fills the @skeleton@ with data read from memory beginning at @ptr@.+The skeleton is needed formally for using 'Trav.Traversable'.+For instance when reading a list, it is not clear,+how many elements shall be read.+Using the skeleton you can give this information+and you also provide information that is not contained in the element type @a@.+For example you can call++> peek (replicate 10 ()) ptr++for reading 10 elements from memory starting at @ptr@.+-}+{-# INLINE peek #-}+peek ::+   (Trav.Traversable f, Storable a) =>+   f () -> Ptr (f a) -> IO (f a)+peek skeleton =+   evalStateT (Trav.mapM (const peekState) skeleton) .+   castPtr++{- |+Like 'peek' but uses 'pure' for construction of the result.+'pure' would be in class @Pointed@ if that would exist.+Thus we use the closest approximate 'Applicative'.+-}+{-# INLINE peekApplicative #-}+peekApplicative ::+   (App.Applicative f, Trav.Traversable f, Storable a) =>+   Ptr (f a) -> IO (f a)+peekApplicative =+   evalStateT (Trav.sequence (App.pure peekState)) . castPtr++{-# INLINE peekState #-}+peekState ::+   (Storable a) =>+   StateT (Ptr a) IO a+peekState =+   get >>= \p -> put (advancePtr p 1) >> liftIO (St.peek p)++{-# INLINE poke #-}+poke ::+   (Fold.Foldable f, Storable a) =>+   Ptr (f a) -> f a -> IO ()+poke ptr x =+   evalStateT (Fold.traverse_ pokeState x) $+   castPtr ptr++{-# INLINE pokeState #-}+pokeState ::+   (Storable a) =>+   a -> StateT (Ptr a) IO ()+pokeState x = do+   liftIO . flip St.poke x =<< get+   modify (flip advancePtr 1)
+ src/Foreign/Storable/TraversableUnequalSizes.hs view
@@ -0,0 +1,94 @@+{- |+If you have a Traversable instance of a record,+you can load and store all elements,+that are accessible by Traversable methods.+In this attempt we support elements of unequal size.+However this can be awfully slow,+since the program might perform size computations at run-time.+-}+module Foreign.Storable.TraversableUnequalSizes (+   alignment, sizeOf,+   peek, poke,+   ) where++import qualified Data.Traversable as Trav+import qualified Data.Foldable as Fold++import Control.Monad.Trans.State+          (StateT, evalStateT, gets, modify, )+import Control.Monad.Trans (liftIO, )++import Foreign.Storable.FixedArray (roundUp, )+import qualified Foreign.Storable as St++import Foreign.Ptr (Ptr, )+import Foreign.Storable (Storable, )+++{-# INLINE alignment #-}+alignment ::+   (Fold.Foldable f, Storable a) =>+   f a -> Int+alignment =+   Fold.foldl' (\n x -> lcm n (St.alignment x)) 1++{-# INLINE sizeOf #-}+sizeOf ::+   (Fold.Foldable f, Storable a) =>+   f a -> Int+sizeOf =+   Fold.foldl' (\s x -> roundUp (St.alignment x) s + St.sizeOf x) 0++{-+This function requires that alignment does not depend on an element value,+because we cannot not know the value before loading it.+Thus @alignment (undefined::a)@ must be defined.+-}+{-# INLINE peek #-}+peek ::+   (Trav.Traversable f, Storable a) =>+   f () -> Ptr (f a) -> IO (f a)+peek skeleton ptr =+   evalStateT (Trav.mapM (const (peekState ptr)) skeleton) 0++{-# INLINE peekState #-}+peekState ::+   (Storable a) =>+   Ptr (f a) -> StateT Int IO a+peekState p = do+   let pseudoPeek :: Ptr (f a) -> a+       pseudoPeek _ = error "Traversable.peek: alignment must not depend on the element value"+   k <- getOffset (pseudoPeek p)+   a <- liftIO (St.peekByteOff p k)+   advanceOffset a+   return a++{-# INLINE poke #-}+poke ::+   (Fold.Foldable f, Storable a) =>+   Ptr (f a) -> f a -> IO ()+poke ptr x =+   evalStateT (Fold.traverse_ (pokeState ptr) x) 0++{-# INLINE pokeState #-}+pokeState ::+   (Storable a) =>+   Ptr (f a) -> a -> StateT Int IO ()+pokeState p a = do+   k <- getOffset a+   liftIO (St.pokeByteOff p k a)+   advanceOffset a++{-# INLINE getOffset #-}+getOffset ::+   (Storable a) =>+   a -> StateT Int IO Int+getOffset a =+   gets (roundUp (St.alignment a))++{-# INLINE advanceOffset #-}+advanceOffset ::+   (Storable a) =>+   a -> StateT Int IO ()+advanceOffset a =+   modify ( + St.sizeOf a)
storable-record.cabal view
@@ -1,74 +1,51 @@ Name:         storable-record-Version:      0.0.1+Version:      0.0.2 Category:     Data, Foreign Synopsis:     Elegant definition of Storable instances for records Description:-  With this package definition+  With this package   you can build a Storable instance of a record type-  from Storable instances of its elements.-  This is as simple as:-  .-  > import Foreign.Storable.Record as Store-  > import Foreign.Storable (Storable (..), )-  >-  > import Control.Applicative (liftA2, )-  >-  > data Stereo a = Stereo (left, right :: a)-  > -- parentheses must be curly braces, but Haddock does not like them-  >-  > store :: Storable a => Store.Dictionary (Stereo a)-  > store =-  >    Store.run $-  >    liftA2 Stereo-  >       (Store.element left)-  >       (Store.element right)-  >-  > instance (Storable a) => Storable (Stereo a) where-  >    sizeOf = Store.sizeOf store-  >    alignment = Store.alignment store-  >    peek = Store.peek store-  >    poke = Store.poke store-  .+  from Storable instances of its elements in an elegant way.+  It does not do any magic,+  just a bit arithmetic to compute the right offsets,+  that would be otherwise done manually+  or by a preprocessor like C2HS.   I cannot promise that the generated memory layout   is compatible with that of a corresponding C struct.   However, the module generates the smallest layout   that is possible with respect to the alignment of the record elements.-  Thus this package might provide a Haskell98 alternative to HSC-  without a preprocessor.   If you encounter, that a record does not have a compatible layout,   we should fix that.   But also without C compatibility this package is useful   e.g. in connection with StorableVector.   .-  The @Stereo@ constructor is exclusively used-  for constructing the @peek@ function,-  where as the accessors in the @element@ calls-  are used for assembling the @poke@ function.-  It is required that the order of arguments of @Stereo@-  matches the record accessors in the @element@ calls.-  If you want that the stored data correctly and fully represents-  your Haskell data, it must hold:+  We provide Storable instance support for several cases:   .-  >   Stereo (left x) (right x) = x   .+  * If you wrap a type in a @newtype@,+    then you can lift its 'Storable' instance to that @newtype@+    with the module "Foreign.Storable.Newtype".+    This way you do not need the @GeneralizedNewtypeDeriving@ feature of GHC.   .-  Unfortunately this cannot be checked automatically.-  However, mismatching types that are caused by swapped arguments-  are detected by the type system.-  Our system performs for you:-  Size and alignment computation, poking and peeking.-  Thus several inconsistency bugs can be prevented using this package,-  like size mismatches space required by @poke@ actions.-  There is no more restriction,-  thus smart constructors and accessors-  and nested records work, too.-  For nested records however,-  I recommend individual Storable instances for the sub-records.+  * If you have a type that is an instance of 'Traversable',+    you can use that feature for implementation of 'Storable' methods.+    The module "Foreign.Storable.Traversable"+    allows manipulation of the portion of your type,+    that is accessible by 'Traversable' methods.+    For instance with the type+    @data T a = Cons Int [a]@+    and an according 'Traversable' implementation,+    you can load and store the elements of the contained list.+    This may be part of a 'Storable' implementation of the whole type.   .-  You see it would simplify class instantiation-  if we could tell the class dictionary at once-  instead of defining each method separately.+  * If you have a record containing elements of various types,+    then you need module "Foreign.Storable.Record".   .-  For examples see packages sox and synthesizer.+  Note however that the Storable instances+  defined with this package are quite slow in (up to) GHC-6.12.1.+  I'm afraid this is due to incomplete inlining,+  but we have still to investigate the problem.+  .+  For examples see packages @storable-tuple@ and @sample-frame@. License:             BSD3 License-file:        LICENSE Author:              Henning Thielemann <storable@henning-thielemann.de>@@ -76,7 +53,7 @@ Homepage:            http://code.haskell.org/~thielema/storable-record/ Stability:           Experimental Build-Type:          Simple-Tested-With:         GHC==6.8.2+Tested-With:         GHC==6.8.2 && ==6.10.4 && ==6.12.1 Cabal-Version:       >=1.6  Source-Repository head@@ -86,7 +63,7 @@ Source-Repository this   Type:     darcs   Location: http://code.haskell.org/~thielema/storable-record/-  Tag:      0.0.1+  Tag:      0.0.2  Flag splitBase   description: Choose the new smaller, split-up base package.@@ -96,7 +73,8 @@     transformers >=0.0.1 && <0.2,     utility-ht >=0.0.1 && <0.1   If flag(splitBase)-    Build-Depends: base >= 3+    Build-Depends:+      base >= 3 && < 6   Else     Build-Depends:       special-functors >= 1.0 && <1.1,@@ -108,5 +86,7 @@   Exposed-Modules:     Foreign.Storable.Record     Foreign.Storable.Newtype+    Foreign.Storable.Traversable   Other-Modules:     Foreign.Storable.FixedArray+    Foreign.Storable.TraversableUnequalSizes