packages feed

chimera 0.3.0.0 → 0.3.1.0

raw patch · 5 files changed

+166/−35 lines, 5 filesdep +adjunctionsdep +distributivedep +gaugedep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: adjunctions, distributive, gauge, mtl, random

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Data.Chimera: instance Control.Monad.Fix.MonadFix (Data.Chimera.Chimera Data.Vector.Vector)
+ Data.Chimera: instance Control.Monad.Reader.Class.MonadReader GHC.Types.Word (Data.Chimera.Chimera Data.Vector.Vector)
+ Data.Chimera: instance Control.Monad.Zip.MonadZip (Data.Chimera.Chimera Data.Vector.Vector)
+ Data.Chimera: instance Data.Distributive.Distributive (Data.Chimera.Chimera Data.Vector.Vector)
+ Data.Chimera: instance Data.Functor.Rep.Representable (Data.Chimera.Chimera Data.Vector.Vector)
+ Data.Chimera: instance GHC.Base.Monad (Data.Chimera.Chimera Data.Vector.Vector)

Files

Data/Chimera.hs view
@@ -6,14 +6,17 @@ -- -- Lazy infinite streams with O(1) indexing. -{-# LANGUAGE CPP                 #-}-{-# LANGUAGE DeriveFoldable      #-}-{-# LANGUAGE DeriveFunctor       #-}-{-# LANGUAGE DeriveTraversable   #-}-{-# LANGUAGE FlexibleContexts    #-}-{-# LANGUAGE FlexibleInstances   #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications    #-}+{-# LANGUAGE BangPatterns          #-}+{-# LANGUAGE CPP                   #-}+{-# LANGUAGE DeriveFoldable        #-}+{-# LANGUAGE DeriveFunctor         #-}+{-# LANGUAGE DeriveTraversable     #-}+{-# LANGUAGE FlexibleContexts      #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables   #-}+{-# LANGUAGE TypeApplications      #-}+{-# LANGUAGE TypeFamilies          #-}  module Data.Chimera   ( -- * Memoization@@ -49,13 +52,20 @@  import Prelude hiding ((^), (*), div, fromIntegral, not, and, or, cycle, iterate, drop) import Control.Applicative+import Control.Monad.Fix+import Control.Monad.Zip import Data.Bits-import Data.Function (fix) import Data.Functor.Identity import qualified Data.Vector as V import qualified Data.Vector.Generic as G import qualified Data.Vector.Unboxed as U +#if DefineRepresentable+import Control.Monad.Reader+import Data.Distributive+import qualified Data.Functor.Rep as Rep+#endif+ import Data.Chimera.Compat import Data.Chimera.FromIntegral @@ -125,6 +135,33 @@   liftA2 f = zipSubvectors (liftA2 f) #endif +instance Monad (Chimera V.Vector) where+  m >>= f = tabulate $ \w -> index (f (index m w)) w++instance MonadFix (Chimera V.Vector) where+  mfix = tabulate . mfix . fmap index++instance MonadZip (Chimera V.Vector) where+  mzip as bs = tabulate (\w -> (index as w, index bs w))+  mzipWith f as bs = tabulate $ \w -> f (index as w) (index bs w)++#if DefineRepresentable++instance MonadReader Word (Chimera V.Vector) where+  ask = Rep.askRep+  local = Rep.localRep++instance Distributive (Chimera V.Vector) where+  distribute = Rep.distributeRep+  collect = Rep.collectRep++instance Rep.Representable (Chimera V.Vector) where+  type Rep (Chimera V.Vector) = Word+  tabulate = tabulate+  index = index++#endif+ bits :: Int bits = fbs (0 :: Word) @@ -145,15 +182,13 @@      (Monad m, G.Vector v a)   => (Word -> m a)   -> m (Chimera v a)-tabulateM f = do-  z  <- f 0-  zs <- V.generateM bits tabulateSubVector-  pure $ Chimera $ G.singleton z `V.cons` zs+tabulateM f = Chimera <$> V.generateM (bits + 1) tabulateSubVector   where     tabulateSubVector :: Int -> m (v a)+    tabulateSubVector 0 = G.singleton <$> f 0     tabulateSubVector i = G.generateM ii (\j -> f (int2word (ii + j)))       where-        ii = 1 `shiftL` i+        ii = 1 `unsafeShiftL` (i - 1)  {-# SPECIALIZE tabulateM :: G.Vector v a => (Word -> Identity a) -> Identity (Chimera v a) #-} @@ -192,17 +227,15 @@ tabulateFixM f = result   where     result :: m (Chimera v a)-    result = do-      z  <- fix f 0-      zs <- V.generateM bits tabulateSubVector-      pure $ Chimera $ G.singleton z `V.cons` zs+    result = Chimera <$> V.generateM (bits + 1) tabulateSubVector      tabulateSubVector :: Int -> m (v a)+    tabulateSubVector 0 = G.singleton <$> fix f 0     tabulateSubVector i = subResult       where         subResult      = G.generateM ii (\j -> f fixF (int2word (ii + j)))         subResultBoxed = V.generateM ii (\j -> f fixF (int2word (ii + j)))-        ii = 1 `shiftL` i+        ii = 1 `unsafeShiftL` (i - 1)          fixF :: Word -> m a         fixF k@@ -245,11 +278,14 @@ -- >>> index ch 9 -- 81 index :: G.Vector v a => Chimera v a -> Word -> a-index (Chimera vs) 0 = G.unsafeHead (V.unsafeHead vs)-index (Chimera vs) i = G.unsafeIndex (vs `V.unsafeIndex` (sgm + 1)) (word2int $ i - 1 `shiftL` sgm)+index (Chimera vs) i =+  (vs `V.unsafeIndex` (fbs i - lz))+  `G.unsafeIndex`+  (word2int (i .&. complement ((1 `shiftL` (fbs i - 1)) `unsafeShiftR` lz)))   where-    sgm :: Int-    sgm = fbs i - 1 - word2int (clz i)+    lz :: Int+    !lz = word2int (clz i)+{-# INLINE index #-}  -- | Convert a stream to an infinite list. --@@ -313,7 +349,7 @@   => (u a -> v b)   -> Chimera u a   -> Chimera v b-mapSubvectors f (Chimera bs) = Chimera (V.map f bs)+mapSubvectors f (Chimera bs) = Chimera (fmap f bs)  -- | Zip subvectors from two streams, using a given length-preserving function. zipSubvectors@@ -322,4 +358,4 @@   -> Chimera u a   -> Chimera v b   -> Chimera w c-zipSubvectors f (Chimera bs1) (Chimera bs2) = Chimera (V.zipWith f bs1 bs2)+zipSubvectors f (Chimera bs1) (Chimera bs2) = Chimera (mzipWith f bs1 bs2)
Data/Chimera/FromIntegral.hs view
@@ -12,10 +12,8 @@   , int2word   ) where -import Unsafe.Coerce- word2int :: Word -> Int-word2int = unsafeCoerce+word2int = fromIntegral  int2word :: Int -> Word-int2word = unsafeCoerce+int2word = fromIntegral
README.md view
@@ -63,7 +63,7 @@ isOddF f n = if n == 0 then False else not (f (n - 1)) ``` -and invoke `tabulateFix` to pass cache into recursive calls as well:+and invoke `memoizeFix` to pass cache into recursive calls as well:  ```haskell isOdd' :: Word -> Bool@@ -95,3 +95,40 @@ isPrime' :: Word -> Bool isPrime' = memoizeFix isPrimeF ```++## Magic and its exposure++Internally `Chimera` is represented as a _boxed_ vector+of growing (possibly, _unboxed_) vectors `v a`:++```haskell+newtype Chimera v a = Chimera (Data.Vector.Vector (v a))+```++Assuming 64-bit architecture, the outer vector consists of 65 inner vectors+of sizes 1, 1, 2, 2<sup>2</sup>, ..., 2<sup>63</sup>. Since the outer vector+is boxed, inner vectors are allocated on-demand only: quite fortunately,+there is no need to allocate all 2<sup>64</sup> elements at once.++To access an element by its index it is enough to find out to which inner+vector it belongs, which, thanks to the doubling pattern of sizes,+can be done instantly by [`ffs`](https://en.wikipedia.org/wiki/Find_first_set)+instruction. The caveat here is+that accessing an inner vector first time will cause its allocation,+taking O(n) time. So to restore _amortized_ O(1) time we must assume+a dense access. `Chimera` is no good for sparse access+over a thin set of indices.++One can argue that this structure is not infinite,+because it cannot handle more than 2<sup>64</sup> elements.+I believe that it is _infinite enough_ and no one would be able to exhaust+its finiteness any time soon. Strictly speaking, to cope with indices out of+`Word` range and `memoize`+[Ackermann function](https://en.wikipedia.org/wiki/Ackermann_function),+one could use more layers of indirection, raising access time+to O([log<sup>*</sup>](https://en.wikipedia.org/wiki/Iterated_logarithm) n).+I still think that it is morally correct to claim O(1) access,+because all asymptotic estimates of data structures+are usually made under an assumption that they contain+less than `maxBound :: Word` elements+(otherwise you can not even treat pointers as a fixed-size data).
+ bench/Bench.hs view
@@ -0,0 +1,42 @@+module Main where++import Control.Monad.State+import Data.Chimera+import Gauge.Main+import System.Random++main :: IO ()+main = defaultMain+  [ bgroup "read/Chimera" (map benchReadChimera [100, 200, 500, 1000])+  , bgroup "read/List"    (map benchReadList    [100, 200, 500, 1000])+  ]++randomChimera :: UChimera Int+randomChimera = flip evalState (mkStdGen 42) $ tabulateM $ const $ do+  g <- get+  let (x, g') = random g+  put g'+  pure x++randomList :: [Int]+randomList = randoms (mkStdGen 42)++randomIndicesWord :: [Word]+randomIndicesWord = randoms (mkStdGen 42)++randomIndicesInt :: [Int]+randomIndicesInt = randoms (mkStdGen 42)++benchReadChimera :: Word -> Benchmark+benchReadChimera n+  = bench (show n)+  $ nf (sum . map (index randomChimera))+  $ map (`rem` n)+  $ take (fromIntegral n) randomIndicesWord++benchReadList :: Int -> Benchmark+benchReadList n+  = bench (show n)+  $ nf (sum . map (randomList !!))+  $ map (`mod` n)+  $ take n randomIndicesInt
chimera.cabal view
@@ -1,5 +1,5 @@ name: chimera-version: 0.3.0.0+version: 0.3.1.0 cabal-version: >=1.10 build-type: Simple license: BSD3@@ -12,7 +12,7 @@ author: Bodigrim extra-source-files:   README.md-tested-with: GHC==8.8.1, GHC==8.6.5, GHC==8.4.4, GHC==8.2.2, GHC==8.0.2+tested-with: GHC==8.10.1, GHC==8.8.3, GHC==8.6.5, GHC==8.4.4, GHC==8.2.2, GHC==8.0.2 description:   There are plenty of memoizing libraries on Hackage, but they   usually fall into two categories:@@ -43,10 +43,15 @@   type: git   location: https://github.com/Bodigrim/chimera +flag representable+  description: Define Representable instance from adjunctions package+  default: True+ library-  build-depends:-    base >=4.9 && <5,-    vector+  build-depends: base >=4.9 && <5, vector+  if flag(representable)+    build-depends: adjunctions, distributive, mtl+    cpp-options: -DDefineRepresentable   exposed-modules:     Data.Chimera     Data.Chimera.ContinuousMapping@@ -71,4 +76,17 @@   main-is: Test.hs   default-language: Haskell2010   hs-source-dirs: test+  ghc-options: -Wall++benchmark bench+  build-depends:+    base,+    chimera,+    gauge,+    mtl,+    random+  type: exitcode-stdio-1.0+  main-is: Bench.hs+  default-language: Haskell2010+  hs-source-dirs: bench   ghc-options: -Wall