packages feed

rock (empty) → 0.1.0.0

raw patch · 11 files changed

+736/−0 lines, 11 filesdep +basedep +dependent-mapdep +dependent-sumsetup-changed

Dependencies added: base, dependent-map, dependent-sum, dependent-sum-template, deriving-compat, mtl, protolude, rock, transformers

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Unreleased++# 0.1.0.0++- Initial release
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Olle Fredriksson (c) 2018-2019++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 Olle Fredriksson 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,106 @@+# rock [![Hackage](https://img.shields.io/hackage/v/rock.svg)](https://hackage.haskell.org/package/rock)++A build system inspired by [Build systems à la carte](https://www.microsoft.com/en-us/research/publication/build-systems-la-carte/) and [Haxl](http://hackage.haskell.org/package/haxl).++Used in [Sixten](https://github.com/ollef/sixten) and+[Sixty](https://github.com/ollef/sixty) to achieve incremental and query driven+compiler architectures.++# Example++```haskell+{-# language GADTs #-}+{-# language NoImplicitPrelude #-}+{-# language OverloadedStrings #-}+{-# language StandaloneDeriving #-}+{-# language TemplateHaskell #-}++import Protolude++import Data.GADT.Compare.TH (deriveGEq, deriveGCompare)+import qualified Rock++data Query a where+  A :: Query Integer+  B :: Query Integer+  C :: Query Integer+  D :: Query Integer++deriving instance Show (Query a)++deriveGEq ''Query+deriveGCompare ''Query++rules :: Rock.Rules Query+rules key = do+  putText $ "Fetching " <> show key+  case key of+    A -> pure 10+    B -> do+      a <- Rock.fetch A+      pure $ a + 20+    C -> do+      a <- Rock.fetch A+      pure $ a + 30+    D ->+      (+) <$> Rock.fetch B <*> Rock.fetch C++main :: IO ()+main = do+  do+    putText "Running"+    result <- Rock.runTask Rock.sequentially rules (Rock.fetch D)+    print result+  do+    putText "Running with memoisation"+    memoVar <- newMVar mempty+    result <-+      Rock.runTask+        Rock.sequentially+        (Rock.memoise memoVar rules)+        (Rock.fetch D)+    print result+  do+    putText "Running with memoisation using the parallel strategy"+    memoVar <- newMVar mempty+    result <-+      Rock.runTask+        Rock.inParallel+        (Rock.memoise memoVar rules)+        (Rock.fetch D)+    print result+```++Prints++```+Running+Fetching D+Fetching B+Fetching A+Fetching C+Fetching A+70+Running with memoisation+Fetching D+Fetching B+Fetching A+Fetching C+70+Running with memoisation using the parallel strategy+Fetching D+Fetching C+Fetching B+Fetching A+70+```++# Related projects++* [Shake](http://hackage.haskell.org/package/shake)+* [Salsa](https://crates.io/crates/salsa)++# Contributions++... are very welcome, especially in the areas of documentation, examples,+testing, and benchmarking.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ examples/Spreadsheet.hs view
@@ -0,0 +1,60 @@+{-# language GADTs #-}+{-# language NoImplicitPrelude #-}+{-# language OverloadedStrings #-}+{-# language StandaloneDeriving #-}+{-# language TemplateHaskell #-}++import Protolude++import Data.GADT.Compare.TH (deriveGEq, deriveGCompare)+import qualified Rock++data Query a where+  A :: Query Integer+  B :: Query Integer+  C :: Query Integer+  D :: Query Integer++deriving instance Show (Query a)++deriveGEq ''Query+deriveGCompare ''Query++rules :: Rock.Rules Query+rules key = do+  putText $ "Fetching " <> show key+  case key of+    A -> pure 10+    B -> do+      a <- Rock.fetch A+      pure $ a + 20+    C -> do+      a <- Rock.fetch A+      pure $ a + 30+    D ->+      (+) <$> Rock.fetch B <*> Rock.fetch C++main :: IO ()+main = do+  do+    putText "Running"+    result <- Rock.runTask Rock.sequentially rules (Rock.fetch D)+    print result+  do+    putText "Running with memoisation"+    memoVar <- newMVar mempty+    result <-+      Rock.runTask+        Rock.sequentially+        (Rock.memoise memoVar rules)+        (Rock.fetch D)+    print result+  do+    putText "Running with memoisation using the parallel strategy"+    memoVar <- newMVar mempty+    result <-+      Rock.runTask+        Rock.inParallel+        (Rock.memoise memoVar rules)+        (Rock.fetch D)+    print result
+ rock.cabal view
@@ -0,0 +1,66 @@+name:                rock+version:             0.1.0.0+synopsis:            A build system for incremental, parallel, and demand-driven computations+description:         See <https://www.github.com/ollef/rock> for more+                     information and+                     <https://github.com/ollef/rock/tree/master/examples> for+                     examples.+homepage:            https://github.com/ollef/rock#readme+license:             BSD3+license-file:        LICENSE+author:              Olle Fredriksson+maintainer:          fredriksson.olle@gmail.com+copyright:           2018-2019 Olle Fredriksson+category:            Development+build-type:          Simple+extra-source-files:+                     README.md+                     CHANGELOG.md+cabal-version:       >=1.10++library+  ghc-options:         -Wall+                       -Wcompat+                       -Widentities+                       -Wincomplete-record-updates+                       -Wincomplete-uni-patterns+                       -Wmissing-home-modules+                       -Wpartial-fields+                       -Wredundant-constraints+                       -Wtabs+                       -funbox-strict-fields+  hs-source-dirs:      src+  exposed-modules:+                       Rock+                       Rock.Core+                       Rock.HashTag+                       Rock.Hashed+                       Rock.Traces+  build-depends:       base >= 4.7 && < 5+                     , dependent-map+                     , dependent-sum+                     , deriving-compat+                     , mtl+                     , transformers+                     , protolude+  default-language:    Haskell2010+  default-extensions:  OverloadedStrings, NoImplicitPrelude++source-repository head+  type:     git+  location: https://github.com/ollef/rock++flag examples+  Description: "Build examples"+  Default:     False+  Manual:      True++executable rock-spreadsheet+  if !flag(examples)+    buildable:         False+  main-is:             Spreadsheet.hs+  ghc-options:         -Wall+                       -threaded+  hs-source-dirs:      examples+  default-language:    Haskell2010+  build-depends:       base, rock, protolude, dependent-sum-template
+ src/Rock.hs view
@@ -0,0 +1,9 @@+module Rock+  ( module Rock.Core+  , module Rock.HashTag+  , Traces+  ) where++import Rock.Core+import Rock.HashTag+import Rock.Traces
+ src/Rock/Core.hs view
@@ -0,0 +1,345 @@+{-# language DefaultSignatures #-}+{-# language DeriveFunctor #-}+{-# language FlexibleInstances #-}+{-# language FunctionalDependencies #-}+{-# language GADTs #-}+{-# language GeneralizedNewtypeDeriving #-}+{-# language MultiParamTypeClasses #-}+{-# language RankNTypes #-}+{-# language ScopedTypeVariables #-}+{-# language UndecidableInstances #-}+module Rock.Core where++import Protolude++import Control.Monad.Cont+import Control.Monad.Identity+import qualified Control.Monad.RWS.Lazy as Lazy+import qualified Control.Monad.RWS.Strict as Strict+import qualified Control.Monad.State.Lazy as Lazy+import qualified Control.Monad.State.Strict as Strict+import Control.Monad.Trans.Maybe+import qualified Control.Monad.Writer.Lazy as Lazy+import qualified Control.Monad.Writer.Strict as Strict+import Data.Dependent.Map(DMap, GCompare)+import qualified Data.Dependent.Map as DMap+import Data.GADT.Compare++import Rock.Hashed+import Rock.HashTag+import Rock.Traces(Traces)+import qualified Rock.Traces as Traces++-------------------------------------------------------------------------------+-- * Types++-- | A function which, given an @f@ query, returns a 'Task' allowed to make @f@+-- queries to compute its result.+type Rules f = GenRules f f++-- | A function which, given an @f@ query, returns a 'Task' allowed to make @g@+-- queries to compute its result.+type GenRules f g = forall a. f a -> Task g a++-- | An @IO@ action that is allowed to make @f@ queries using the 'fetch'+-- method from its 'MonadFetch' instance.+newtype Task f a = Task { unTask :: IO (Result f a) }++-- | The result of a @Task@, which is either done or wanting to make one or+-- more @f@ queries.+data Result f a+  = Done a+  | Blocked !(BlockedTask f a)++data BlockedTask f a where+  BlockedTask :: Block f a -> (a -> Task f b) -> BlockedTask f b++data Block f a where+  Fetch :: f a -> Block f a+  Ap :: !(BlockedTask f (a -> b)) -> !(BlockedTask f a) -> Block f b++-------------------------------------------------------------------------------+-- * Fetch class++-- | Monads that can make @f@ queries by 'fetch'ing them.+class Monad m => MonadFetch f m | m -> f where+  fetch :: f a -> m a+  default fetch+    :: (MonadTrans t, MonadFetch f m1, m ~ t m1)+    => f a+    -> m a+  fetch = lift . fetch++instance MonadFetch f m => MonadFetch f (ContT r m)+instance MonadFetch f m => MonadFetch f (ExceptT e m)+instance MonadFetch f m => MonadFetch f (IdentityT m)+instance MonadFetch f m => MonadFetch f (MaybeT m)+instance MonadFetch f m => MonadFetch f (ReaderT r m)+instance (MonadFetch f m, Monoid w) => MonadFetch f (Strict.RWST r w s m)+instance (MonadFetch f m, Monoid w) => MonadFetch f (Lazy.RWST r w s m)+instance MonadFetch f m => MonadFetch f (Strict.StateT s m)+instance MonadFetch f m => MonadFetch f (Lazy.StateT s m)+instance (Monoid w, MonadFetch f m) => MonadFetch f (Strict.WriterT w m)+instance (Monoid w, MonadFetch f m) => MonadFetch f (Lazy.WriterT w m)++-------------------------------------------------------------------------------+-- Instances++instance Functor (Task f) where+  {-# INLINE fmap #-}+  fmap f (Task t) = Task $ fmap f <$> t++-- Note: This instance might not fully evaluate @t1@ before @t2@ in+-- @t1 '<*>' t2@ in case @t1@ performs a query using 'fetch'. If this+-- is not desirable, use 'Sequential'.+instance Applicative (Task f) where+  {-# INLINE pure #-}+  pure = Task . pure . Done+  {-# INLINE (<*>) #-}+  Task mrf <*> Task mrx = Task $ (<*>) <$> mrf <*> mrx++instance Monad (Task f) where+  {-# INLINE (>>) #-}+  (>>) = (*>)+  {-# INLINE (>>=) #-}+  Task ma >>= f = Task $ do+    ra <- ma+    case ra of+      Done a -> unTask $ f a+      Blocked (BlockedTask b k) -> return $ Blocked $ BlockedTask b $ k >=> f++instance MonadIO (Task f) where+  {-# INLINE liftIO #-}+  liftIO io = Task $ pure <$> io++instance MonadFetch f (Task f) where+  fetch key = Task $ pure $ Blocked $ BlockedTask (Fetch key) pure++instance Functor (Result f) where+  {-# INLINE fmap #-}+  fmap f (Done x) = Done $ f x+  fmap f (Blocked b) = Blocked $ f <$> b++instance Applicative (Result f) where+  {-# INLINE pure #-}+  pure = Done+  {-# INLINE (<*>) #-}+  Done f <*> Done x = Done $ f x+  Done f <*> Blocked b = Blocked $ f <$> b+  Blocked b <*> Done x = Blocked $ ($ x) <$> b+  Blocked b1 <*> Blocked b2 = Blocked $ BlockedTask (Ap b1 b2) pure++instance Monad (Result f) where+  {-# INLINE (>>) #-}+  (>>) = (*>)+  {-# INLINE (>>=) #-}+  Done x >>= f = f x+  Blocked (BlockedTask b t) >>= f = Blocked $ BlockedTask b $ t >=> Task . pure . f++instance Functor (BlockedTask f) where+  {-# INLINE fmap #-}+  fmap f (BlockedTask b t) = BlockedTask b $ fmap f <$> t++-------------------------------------------------------------------------------+-- * Transformations++-- | Transform the type of queries that a 'Task' performs.+transFetch+  :: (forall b. f b -> Task f' b)+  -> Task f a+  -> Task f' a+transFetch f task = Task $ do+  result <- unTask task+  case result of+    Done a -> return $ Done a+    Blocked b -> unTask $ transFetchBlockedTask f b++transFetchBlockedTask+  :: (forall b. f b -> Task f' b)+  -> BlockedTask f a+  -> Task f' a+transFetchBlockedTask f (BlockedTask b t) = do+  a <- transFetchBlock f b+  transFetch f $ t a++transFetchBlock+  :: (forall b. f b -> Task f' b)+  -> Block f a+  -> Task f' a+transFetchBlock f (Fetch k) = f k+transFetchBlock f (Ap b1 b2) = transFetchBlockedTask f b1 <*> transFetchBlockedTask f b2++-------------------------------------------------------------------------------+-- * Strategies++-- | A 'Strategy' specifies how two queries are performed in an 'Applicative'+-- context.+type Strategy = forall a b. IO (a -> b) -> IO a -> IO b++-- | Runs the two queries in sequence.+sequentially :: Strategy+sequentially = (<*>)++-- | Runs the two queries in parallel.+inParallel :: Strategy+inParallel mf mx = withAsync mf $ \af -> do+  x <- mx+  f <- wait af+  return $ f x++-- | Uses the underlying instances, except for the Applicative instance which+-- is defined in terms of 'return' and '(>>=)'.+--+-- When used with 'Task', i.e. if you construct @m :: 'Sequential' ('Task' f)+-- a@, this means that fetches within @m@ are done sequentially.+newtype Sequential m a = Sequential { runSequential :: m a }+  deriving (Functor, Monad, MonadIO, MonadFetch f)++-- | Defined in terms of 'return' and '(>>=)'.+instance Monad m => Applicative (Sequential m) where+  {-# INLINE pure #-}+  pure = Sequential . return+  {-# INLINE (<*>) #-}+  Sequential mf <*> Sequential mx = Sequential $ mf >>= \f -> fmap f mx++-------------------------------------------------------------------------------+-- * Running tasks++-- | Perform a 'Task', fetching dependency queries from the given 'Rules' function and using the given 'Strategy' for fetches in an 'Applicative' context.+runTask :: Strategy -> Rules f -> Task f a -> IO a+runTask strategy rules task = do+  result <- unTask task+  case result of+    Done a -> return a+    Blocked b -> runBlockedTask strategy rules b++runBlockedTask :: Strategy -> Rules f -> BlockedTask f a -> IO a+runBlockedTask strategy rules (BlockedTask b f) = do+  a <- runBlock strategy rules b+  runTask strategy rules $ f a++runBlock :: Strategy -> Rules f -> Block f a -> IO a+runBlock strategy rules (Fetch key) =+  runTask strategy rules $ rules key+runBlock strategy rules (Ap bf bx) =+  strategy (runBlockedTask strategy rules bf) (runBlockedTask strategy rules bx)++-------------------------------------------------------------------------------+-- * Task combinators++-- | Track the query dependencies of a 'Task' in a 'DMap'+track :: forall f a. GCompare f => Task f a -> Task f (a, DMap f Identity)+track task = do+  depsVar <- liftIO $ newMVar mempty+  let+    record :: f b -> Task f b+    record key = do+      value <- fetch key+      liftIO $ modifyMVar_ depsVar $ pure . DMap.insert key (Identity value)+      return value+  result <- transFetch record task+  deps <- liftIO $ readMVar depsVar+  return (result, deps)++-- | Remember what @f@ queries have already been performed and their results in+-- a 'DMap', and reuse them if a query is performed again a second time.+--+-- The 'DMap' should typically not be reused if there has been some change that+-- might make a query return a different result.+memoise+  :: forall f g+  . GCompare f+  => MVar (DMap f MVar)+  -> GenRules f g+  -> GenRules f g+memoise startedVar rules (key :: f a) =+  join $ liftIO $ modifyMVar startedVar $ \started ->+    case DMap.lookup key started of+      Nothing -> do+        valueVar <- newEmptyMVar+        return+          ( DMap.insert key valueVar started+          , do+            value <- rules key+            liftIO $ putMVar valueVar value+            return value+          )+      Just valueVar ->+        return (started, liftIO $ readMVar valueVar)++-- | Remember the results of previous @f@ queries and what their dependencies+-- were then.+--+-- If all dependencies of a 'NonInput' query are the same, reuse the old result.+-- The 'DMap' _can_ be reused if there are changes to 'Input' queries.+verifyTraces+  :: (GCompare f, HashTag f)+  => MVar (Traces f)+  -> GenRules (Writer TaskKind f) f+  -> Rules f+verifyTraces tracesVar rules key = do+  traces <- liftIO $ readMVar tracesVar+  maybeValue <- case DMap.lookup key traces of+    Nothing -> return Nothing+    Just oldValueDeps ->+      Traces.verifyDependencies fetchHashed oldValueDeps+  case maybeValue of+    Nothing -> do+      ((value, taskKind), deps) <- track $ rules $ Writer key+      case taskKind of+        Input ->+          return ()+        NonInput ->+          liftIO $ modifyMVar_ tracesVar+            $ pure+            . Traces.record key value deps+      return value+    Just value -> return value+  where+    fetchHashed :: HashTag f => f a -> Task f (Hashed a)+    fetchHashed key' = hashed key' <$> fetch key'++data TaskKind+  = Input -- ^ Used for tasks whose results can change independently of their fetched dependencies, i.e. inputs.+  | NonInput -- ^ Used for task whose results only depend on fetched dependencies.++-- | A query that returns a @w@ alongside the ordinary @a@.+data Writer w f a where+  Writer :: f a -> Writer w f (a, w)++instance GEq f => GEq (Writer w f) where+  geq (Writer f) (Writer g) = case geq f g of+    Nothing -> Nothing+    Just Refl -> Just Refl++instance GCompare f => GCompare (Writer w f) where+  gcompare (Writer f) (Writer g) = case gcompare f g of+    GLT -> GLT+    GEQ -> GEQ+    GGT -> GGT++-- | @'writer' write rules@ runs @write w@ each time a @w@ is returned from a+-- rule in @rules@.+writer+  :: forall f w g+  . (forall a. f a -> w -> Task g ())+  -> GenRules (Writer w f) g+  -> GenRules f g+writer write rules key = do+  (res, w) <- rules $ Writer key+  write key w+  return res++-- | @'traceFetch' before after rules@ runs @before q@ before a query is+-- performed from @rules@, and @after q result@ every time a query returns with+-- result @result@. +traceFetch+  :: (forall a. f a -> Task g ())+  -> (forall a. f a -> a -> Task g ())+  -> GenRules f g+  -> GenRules f g+traceFetch before after rules key = do+  before key+  result <- rules key+  after key result+  return result
+ src/Rock/HashTag.hs view
@@ -0,0 +1,19 @@+module Rock.HashTag where++import Protolude++-- | Hash the result of a @k@ query.+--+-- A typical implementation looks like:+--+-- @+-- data Query a where+--   ReadFile :: 'FilePath' -> Query 'Text'+--+-- instance 'HashTag' Query where+--   'hashTagged' query =+--     case query of+--       ReadFile {} -> 'hash'+-- @+class HashTag k where+  hashTagged :: k a -> a -> Int
+ src/Rock/Hashed.hs view
@@ -0,0 +1,30 @@+module Rock.Hashed(Hashed, hashed, unhashed) where++import Protolude++import Data.Functor.Classes+import Text.Show++import Rock.HashTag++data Hashed a = Hashed !a !Int+  deriving (Show)++instance Eq a => Eq (Hashed a) where+  Hashed v1 h1 == Hashed v2 h2 = h1 == h2 && v1 == v2++instance Ord a => Ord (Hashed a) where+  compare (Hashed v1 _) (Hashed v2 _) = compare v1 v2++instance Show1 Hashed where+  liftShowsPrec showa _ d (Hashed a _) = showParen (d > 10)+    $ showString "Hashed " . showa 11 a++instance Hashable (Hashed a) where+  hashWithSalt s (Hashed _ h) = hashWithSalt s h++unhashed :: Hashed a -> a+unhashed (Hashed x _) = x++hashed :: HashTag f => f a -> a -> Hashed a+hashed k v = Hashed v $ hashTagged k v
+ src/Rock/Traces.hs view
@@ -0,0 +1,64 @@+{-# language RankNTypes #-}+{-# language StandaloneDeriving #-}+{-# language TemplateHaskell #-}+{-# language UndecidableInstances #-}+module Rock.Traces where++import Protolude++import Data.Dependent.Map(DMap, GCompare, DSum((:=>)))+import qualified Data.Dependent.Map as DMap+import Data.Dependent.Sum+import Data.Functor.Classes+import Text.Show.Deriving++import Rock.HashTag+import Rock.Hashed++data ValueDeps f a = ValueDeps+  { value :: !(Hashed a)+  , dependencies :: !(DMap f Hashed)+  }++return []++deriving instance (Show a, ShowTag f Hashed) => Show (ValueDeps f a)++instance ShowTag f Hashed => Show1 (ValueDeps f) where+  liftShowsPrec = $(makeLiftShowsPrec ''ValueDeps)++type Traces f = DMap f (ValueDeps f)++verifyDependencies+  :: Monad m+  => (forall a'. f a' -> m (Hashed a'))+  -> ValueDeps f a+  -> m (Maybe a)+verifyDependencies fetchHash (ValueDeps hashedValue deps) = do+  upToDate <- allM (DMap.toList deps) $ \(depKey :=> depValue) -> do+    depValue' <- fetchHash depKey+    return $ hash depValue == hash depValue'+  return $ if upToDate+    then Just $ unhashed hashedValue+    else Nothing+  where+    allM :: Monad m => [a] -> (a -> m Bool) -> m Bool+    allM [] _ = return True+    allM (x:xs) p = do+      b <- p x+      if b then+        allM xs p+      else+        return False++record+  :: (GCompare f, HashTag f)+  => f a+  -> a+  -> DMap f Identity+  -> Traces f+  -> Traces f+record k v deps+  = DMap.insert k+  $ ValueDeps (hashed k v)+  $ DMap.mapWithKey (\k' (Identity v') -> hashed k' v') deps