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 +12/−0
- src/Foreign/Storable/Record.hs +55/−2
- src/Foreign/Storable/Traversable.hs +130/−0
- src/Foreign/Storable/TraversableUnequalSizes.hs +94/−0
- storable-record.cabal +36/−56
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