Skip to content

Instantly share code, notes, and snippets.

@ndmitchell
Created July 2, 2016 19:56
Show Gist options
  • Save ndmitchell/31a2f6282d388c172a302f4b802ef419 to your computer and use it in GitHub Desktop.
Save ndmitchell/31a2f6282d388c172a302f4b802ef419 to your computer and use it in GitHub Desktop.
Shake - rethinking rules

I suspect the below isn't a good idea, because the remember needs repeating lots of times for every user of a rule. The new design seems to eliminate these issues more cleanly, but it's an interesting point in the design space.

Rethinking Rules

The current Rule typeclass has a number of issues. It's a bit complex to use, it often results in more dependencies than you were hoping, it's hard to control. People often end up defining oracles instead or rules but then you don't get options for other things. This document attempts to rethink that.

Concept

Shake runs rules that produce values. These rules track the current state of the system (other rules) and external values (files on disk). Currently the value produced by a rule is tracked automatically. This is often awkward. Imagine we want:

 foo : bar; cp bar foo        
 bar : baz; cp baz bar

Currently, if we manually touch bar, then bar rebuilds (since its value changed) and foo rebuilds (since bar changes). What if we only want foo to rebuild? That's currently very difficult to arrange.

New design

apply :: [k] -> Action [v]

Apply, much as before. Doesn't work unless we cache the output value, so we can return it if we decide it doesn't change. And to do that, we have to store the output value. Maybe we also need:

apply_ :: [k] -> Action ()

That way we can put File and Files behind the same input.

register :: (ShakeValue from, Typeable to) => (from -> Maybe (Action to)) -> Rules ()

The register function now doesn't have a storable result - the result is passed back to apply, but is not stored or tracked.

remember :: (ShakeValue from, ShakeValue to) => from -> to -> Action ()

Rule call remember which remembers a fact about the world.

confirm :: ShakeValue fact => (ShakeOptions -> from -> to -> IO (Result to)) -> Rules ()

The confirm function checks a remembered fact. Result will be something like:

data Result x = Equal | ChangeEqual x | ChangeDifferent x | ChangeGone

There would be a more efficient registerAt which can be used to form a big lookup table of registered functions.

registerAt :: (ShakeValue from, Typeable to) => from -> Action to -> Rules ()
registerAt a b = register (\x -> if x == a then Just b else Nothing)

There would be a way to register against the current key (might be slightly more efficient):

rememberSelf :: ShakeValue to => to -> Action ()

Should we have an explicit share function that shares a chunk of the tree?

Examples

The file rules would be roughly:

priority 0 $ register $ \(s :: FilePath) -> Just $ do
    h <- hash <$> readFile s
    remember s h
    return ()

rememberImplicit :: ShakeValue fact -> fact -> Action () rememberImplicit = remember with the current key as well

confirm :: ShakeValue fact => ShakeOptions -> (Maybe fact -> IO (Result fact)) -> Rules () -- Just = confirm the fact is True, tell me details about that -- Nothing = please generate the fact, if you can (assume clean mode)

sharing? caching?

alwaysRerun is just remember () (), with check that always returns False plus says something about

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment