diff options
author | James Foster <jf16688@my.bristol.ac.uk> | 2019-08-17 20:24:15 -0400 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2019-08-22 18:47:20 -0400 |
commit | 605bce26945596e9226c3f52484837a19f1d94c5 (patch) | |
tree | 7f97afda4bd63fa5d391b84be966df0d0c5df2ac /hadrian/doc | |
parent | a33bad2db8baccdf1d8f7c40bac7039c949c8190 (diff) | |
download | haskell-605bce26945596e9226c3f52484837a19f1d94c5.tar.gz |
Add documentation for Hadrian expressions
This commit adds documentation on Hadrian's 'Expr' type and
references the documentation in hadrian/README.md
Diffstat (limited to 'hadrian/doc')
-rw-r--r-- | hadrian/doc/expressions.md | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/hadrian/doc/expressions.md b/hadrian/doc/expressions.md new file mode 100644 index 0000000000..1831bcce96 --- /dev/null +++ b/hadrian/doc/expressions.md @@ -0,0 +1,329 @@ +# Expressions + +`Expr c b a` is a computation that produces a value of type `Action a` and can +read parameters of the current build `Target c b`, but what does that mean +exactly? Here's its definition from `hadrian/src/Hadrian/Expression.hs`: + +```haskell +newtype Expr c b a = Expr (ReaderT (Target c b) Action a) + deriving (Applicative, Functor, Monad) +``` + +So `Expr c b a` is a `newtype` wrapper around a `ReaderT (Target c b) Action a`. +In practice within Hadrian `c` is always `Context` and `b` is always `Builder`. +The extra parameterisation is there so that hopefully one day the general +functionality of Hadrian (eg. compiling a Haskell library) will be available +to Shake users via a library. + +A type synonym from `hadrian/src/Expression/Type.hs` is often used to avoid +writing `Context` and `Builder` everywhere: + +```haskell +type Expr a = H.Expr Context Builder a +``` + +Where `H.Expr` is the `Expr c b a` defined above. The following references to +`Expr` will generally refer to this type synonym unless there is extra +parameterisation. + +Let's break down the type a bit, working from the outside in, left to right. + +## ReaderT + +Put simply, `ReaderT (Target c b) Action a` adds a read-only environment +`Target c b` (in the case of Hadrian: `Target Context Builder`) to values of +type `Action a`. It's the equivalent of threading through a `Target c b` +parameter to all our functions, but we only have to worry about it when we need +it, using `ask :: Monad m => ReaderT r m r` (where `r` is `Target c b` and `m` +is `Action` in this case) or other functions based on it. `ReaderT` and `ask` +are defined in [`Control.Monad.Trans.Reader`](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Reader.html). + +So, instead of: + +```haskell +foo :: Target Context Builder -> Action () +foo target = do + liftIO $ putStrLn "Some message" + bar target + +bar :: Target Context Builder -> Action () +bar target' = do + liftIO $ putStrLn "Some other message" + baz target' + +baz :: Target Context Builder -> Action () +baz target'' = do + liftIO $ putStrLn "Yet another message" + liftIO $ print target +``` + +We can write: + +```haskell +foo :: ReaderT (Target Context Builder) Action () +foo = do + liftIO $ putStrLn "Some message" + bar + +bar :: ReaderT (Target Context Builder) Action () +bar = do + liftIO $ putStrLn "Some other message" + baz + +baz :: ReaderT (Target Context Builder) Action () +baz = do + liftIO $ putStrLn "Yet another message" + target <- ask + liftIO $ print target +``` + +And to make those into Hadrian Expressions all we have to do is change the type +and add the constructor: + +```haskell +foo :: Expr () +foo = Expr $ do + liftIO $ putStrLn "Some message" + bar + +bar :: Expr () +bar = Expr $ do + liftIO $ putStrLn "Some other message" + baz + +baz :: Expr () +baz = Expr $ do + liftIO $ putStrLn "Yet another message" + target <- ask + liftIO $ print target +``` + +## Target + +From `hadrian/src/Hadrian/Target.hs`: + +> Each invocation of a builder is fully described by a `Target`, which +> comprises a build context (type variable `c`), a builder (type variable `b`), +> a list of input files and a list of output files. For example: +> +> ```haskell +> preludeTarget = Target (GHC.Context) (GHC.Builder) +> { context = Context Stage1 base profiling +> , builder = Ghc Stage1 +> , inputs = ["libraries/base/Prelude.hs"] +> , outputs = ["build/stage1/libraries/base/Prelude.p_o"] } +> ``` + +The data type is as follows and is fairly self-explanatory: + +```haskell +data Target c b = Target + { context :: c -- ^ Current build context + , builder :: b -- ^ Builder to be invoked + , inputs :: [FilePath] -- ^ Input files for the builder + , outputs :: [FilePath] -- ^ Files to be produced + } deriving (Eq, Generic, Show) +``` + +So we have some `inputs` to our target, some `outputs` that it will produce, a +context for the build (in Hadrian: `Context`), and the builder (in Hadrian: +`Builder`). + +### Context + +From `hadrian/src/Context/Type.hs`: + +```haskell +data Context = Context + { stage :: Stage -- ^ Currently build Stage + , package :: Package -- ^ Currently build Package + , way :: Way -- ^ Currently build Way (usually 'vanilla') + } deriving (Eq, Generic, Show) +``` + +So Context is a data type that stores a Stage, Package, and a Way, i.e. the +context for some particular `Target`. + +#### Stage + +From `hadrian/src/Stage.hs`: + +```haskell +data Stage = Stage0 | Stage1 | Stage2 | Stage3 + deriving (Show, Eq, Ord, Enum, Generic, Bounded) +``` + +#### Package + +From `hadrian/src/Hadrian/Package.hs`: + +```haskell +data Package = Package { + -- | The package type. 'Library' and 'Program' packages are supported. + pkgType :: PackageType, + -- | The package name. We assume that all packages have different names, + -- hence two packages with the same name are considered equal. + pkgName :: PackageName, + -- | The path to the package source code relative to the root of the build + -- system. For example, @libraries/Cabal/Cabal@ and @ghc@ are paths to the + -- @Cabal@ and @ghc-bin@ packages in GHC. + pkgPath :: FilePath + } deriving (Eq, Generic, Ord, Show) +``` + +`PackageType` is simply defined as: + +```haskell +data PackageType = Library | Program deriving (Eq, Generic, Ord, Show) +``` + +This doesn't quite reflect how Cabal packages are actually structured, as +discussed in https://github.com/snowleopard/hadrian/issues/12, but Hadrian can +still function treating packages as either libraries or programs. + +Both `PackageName` and `FilePath` are just type synonyms of `String`. + +#### Way + +From `hadrian/src/Way/Type.hs`: + +```haskell +newtype Way = Way IntSet +``` + +Where `Way` is a set of enumerated `WayUnit`s wrapped in a `newtype`. + +`WayUnit` is defined as: + +```haskell +data WayUnit = Threaded + | Debug + | Profiling + | Logging + | Dynamic + deriving (Bounded, Enum, Eq, Ord) +``` + +There are also some helper functions in this module to abstract away this +complexity. For example: + +```haskell +import qualified Data.IntSet as Set + +wayFromUnits :: [WayUnit] -> Way +wayFromUnits = Way . Set.fromList . map fromEnum +``` + +`wayFromUnits` converts the `[WayUnit]` into `[Int]` using `map fromEnum`, +creates an `IntSet` from them using `Set.fromList`, and then wraps the `IntSet` +with the `Way` constructor. So we can use `wayFromUnits` to create a `Way` that +builds Hadrian with both multi-threading and profiling by simply writing +`wayFromUnits [Threaded, Profiling]`. + +We can also check if a `Way` contains a particular `WayUnit` by using +`wayUnit :: WayUnit -> Way -> Bool`. This is useful if we need to do something +when we're building with a particular `WayUnit`, but not otherwise. + +For example, using `getWay :: Expr Context b Way` from `hadrian/src/Context.hs`: + +```haskell +foo :: Expr () +foo = do + w <- getWay + if wayUnit Profiling w + then liftIO $ putStrLn "We're building this target with profiling" + else liftIO $ putStrLn "We're not building this target with profiling" +``` + +### Builder + +From `hadrian/src/Builder.hs`: + +> A `Builder` is a (usually external) command invoked in a separate process +> via `cmd`. Here are some examples: +> * `Alex` is a lexical analyser generator that builds `Lexer.hs` from `Lexer.x`. +> * `Ghc` `Stage0` is the bootstrapping Haskell compiler used in `Stage0`. +> * `Ghc` `StageN` (N > 0) is the GHC built in stage (N - 1) and used in `StageN`. +> +> The `Cabal` builder is unusual in that it does not correspond to an external +> program but instead relies on the Cabal library for package configuration. + +The data type itself is simply a long set of constructors that may or may not +be parameterised: + +```haskell +data Builder = Alex + | Ar ArMode Stage + | Autoreconf FilePath + | DeriveConstants + | Cabal ConfigurationInfo Stage + ... + | Ghc GhcMode Stage + ... etc. + deriving (Eq, Generic, Show) +``` + +## Action + +`Action` comes from Shake, the library underlying Hadrian. It can perform `IO` +using `liftIO` and keeps track of the dependencies for a rule. For more +information on `Action`, see the Shake docs: +https://hackage.haskell.org/package/shake-0.18.3/docs/Development-Shake.html + +# Predicates + +One useful kind of Hadrian expression is `Predicate`, which is just a type +synonym for `Expr Bool`. These expressions can read from the `Target` and +possibly perform `IO` or any other `Action` to return a `Bool`. + +A particularly useful operator for using `Predicate`s is `?`. Its real type and +implementation can be found in `hadrian/src/Hadrian/Expression.hs`, but for the +sake of illustrating how it works in most cases, imagine it's defined like +this: + +```haskell +(?) :: Monoid a => Predicate -> Expr a -> Expr a +predicate ? expr = do + bool <- predicate + if bool then expr else return mempty +``` + +If the `Predicate` returns `True`, we return the `Expr` we give it, otherwise +we return `mempty` (which is why we need the `Monoid` type constraint). In fact +thanks to some added type class complexity in the real definition, we can +give `?` a `Bool` instead of a `Predicate` and it works the same way. + +To show how we might use `Predicate`s and `?` in practice, say we want to +compile all the Haskell modules in `compiler/` with `-O0` during stage 0. We can +do that by going to `UserSettings.hs` (see +[the user settings docs](user-settings.md)) and changing `userArgs` to: + +```haskell +userArgs :: Args +userArgs = package compiler ? builder (Ghc CompileHs stage0) ? arg "-O0" +``` + +`Args` is just a type synonym for `Expr [String]` and `arg` just lifts a +`String` into an `Args`. + +`package :: Package -> Predicate` from `hadrian/src/Expression.hs` takes a +`Package` and returns a `Predicate` that will return `True` if the current +`Target` is part of that package and `False` otherwise. In this case we give +it `compiler` which is defined in `hadrian/src/Packages.hs` along with many +other convenient `Package` definitions. + +`builder` comes from `hadrian/src/Expression.hs`: + +> This type class allows the user to construct both precise builder +> predicates, such as `builder (Ghc CompileHs Stage1)`, as well as predicates +> covering a set of similar builders. For example, `builder (Ghc CompileHs)` +> matches any stage, and `builder Ghc` matches any stage and any GHC mode. + +```haskell +class BuilderPredicate a where + -- | Is a particular builder being used? + builder :: a -> Predicate +``` + +Other useful `Predicate` functions can be found in `hadrian/src/Expression.hs` +and `hadrian/src/Hadrian/Expression.hs`. |