How to script with stack
How to Script with Stack
How to create a stack project
How to Build with Stack
Write bitrot free code with Haskell
Practical Haskell: Bitrot-free Scripts

Upgrading haskell

1
2
3
4
5
6
7
8
9
#!/bin/bash
export TTY

. $HOME/scripts/libraries/bash-library.sh
export PATH="$HOME/.local/bin:$(remove_from_path $SCRIPTS)"

cabal install Cabal cabal-install
cabal update
sudo stack upgrade

Developing cabal packages

https://cabal.readthedocs.io/en/latest/developing-packages.html

  • Given a directory of haskell (.hs) files, to turn this into a Cabal package we need two extra files in the project’s root directory:
    • proglet.cabal
      Containing package metadata and build information.
    • Setup.hs
      Fsually containing a few standardized lines of code, but can be customized if necessary.
1
cabal init --interactive

Start a GHCi shell with a cabal package

https://www.haskell.org/cabal/users-guide/nix-local-build.html#how-can-i-profile-my-library-application

1
2
cd $MYGIT/uber/queryparser
cabal v2-repl

How to compile a project with cabal

1
2
3
cd $MYGIT/uber/queryparser
ls queryparser.cabal # This file exists here
cabal v2-build

Profiling wiht cabal

https://www.haskell.org/cabal/users-guide/nix-local-build.html#how-can-i-profile-my-library-application

How to script with stack

Install ghc

1
2
3
stack setup
# or
stack --install-ghc exec -- ghc --version

Get the ghc version

1
stack exec -- ghc --version

Compile a simple program

1
2
main :: IO ()
main = putStrLn "Hello World!"
1
stack exec -- ghc HelloWorld.hs

Interpret a simple program as a script

1
2
3
stack exec -- runghc HelloWorld.hs
# Or, as you may have guessed:
stack runghc -- HelloWorld.hs

Add packages

1
hs-import-to-package Network.HTTP.Simple
http-conduit
HTTP-Simple-0.1
http-conduit-2.3.0
http-client
HTTP-4000.3.14
http-streams

Let’s say we want to use a package that doesn’t ship with GHC itself, like http-conduit.

The stack exec command and its shortcuts like stack ghc and stack runghc all take a --package argument indicating that an additional package needs to be present.

So consider this http.hs file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Lazy.Char8 as L8
import           Network.HTTP.Simple

main :: IO ()
main = do
    response <- httpLBS "http://httpbin.org/get"

    putStrLn $ "The status code was: " ++
               show (getResponseStatusCode response)
    print $ getResponseHeader "Content-Type" response
    L8.putStrLn $ getResponseBody response
1
2
3
4
NonDet -- backtracking search
WriterT --
ReaderT --
StateT -- threads state through a function
1
2
# ensure that it runs with the http-conduit package
stack runghc --package http-conduit -- http.hs

Specify a resolver

There’s unfortunately a major downside to everything we’ve seen so far: there are no guarantees given about which version of GHC or libraries will be used.

The selection will depend entirely on what Stack deems the most appropriate resolver - or collection of GHC and libraries - when you start using it.

If you want to be guaranteed that a specific set of packages will be used, you can set the --resolver on the command line.

$ stack --resolver lts-12.21 runghc --package http-conduit http.hs

Script interpreter

https://docs.haskellstack.org/en/stable/GUIDE/#script-interpreter

Remembering to pass all of these flags on the command line is very tedious, error prone, and makes it difficult to share scripts with others.

To address this, Stack has a [script interpreter feature](https://docs.ha skellstack.org/en/stable/GUIDE/#script-%20interpreter) which allows these flags to be placed at the top of your script.

Stack also has a dedicated script command which has some nice features like auto- detection of packages you need based on import statements.

If we modify our http.hs to say:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/usr/bin/env stack
-- stack --resolver lts-12.21 script
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Lazy.Char8 as L8
import           Network.HTTP.Simple

main :: IO ()
main = do
    response <- httpLBS "http://httpbin.org/get"

    putStrLn $ "The status code was: " ++
               show (getResponseStatusCode response)
    print $ getResponseHeader "Content-Type" response
    L8.putStrLn $ getResponseBody response

1
tf hs | xa stack
The status code was: 200
["application/json"]
{
  "args": {},
  "headers": {
    "Accept-Encoding": "gzip",
    "Host": "httpbin.org",
    "X-Amzn-Trace-Id": "Root=1-5ee17e9a-035e13cc316aa1aeb985dc67"
  },
  "origin": "123.255.57.26",
  "url": "http://httpbin.org/get"
}

If you want to create self contained scripts, a script interpreter line at the top of your file is a highly recommended practice.

How to create a stack project

Start a new project

You can start a new project with the command:

1
stack new my-project-name

This will use the default project template. You can also choose from many available templates which you can list with

1
stack templates

For example, if you wanted to the simple template instead, you could run:

1
stack new my-project-name simple

You should now have a directory named my-project-name (or whatever string you used), with a stack.yaml file inside of it. A stack.yaml file must be present in the root directory of each project, and provides a number of settings. You can edit that file and view the comments, or view the configuration file documentation.

Convert an existing package

If you have an existing Cabal package, you can use the stack init command inside the package directory to initialize a stack.yaml file. Stack will attempt to determine a package set compatible with the packages requested in your .cabal file.

Project versus package

Note the difference in terminology above. This is important: a cabal package is identified by a single .cabal file, and has 0 or 1 libraries, and 0 or more executables, test suites, and benchmarks.

A Stack project has 1 or more cabal packages, and can build them all at the same time. In likely the majority of cases, your Stack project will have just one cabal package in it. However, multi-package projects can be very common for both open source library collections and for structuring commercial applications. Some open source examples of multi-package projects include Yesod, WAI, and Servant.

Building

The basic command for building your project is

1
stack build

Very likely, you’ll need to first tell Stack to install the appropriate GHC version for your project. You can do this with:

1
stack setup

or by using the --install-ghc option to stack build:

1
stack --install-ghc build

Running executables

Let’s suppose your project defines an executable called my-executable. How do you run it? There are three common ways:

  1. stack exec my-executable will modify your PATH variable to include a number of additional directories, including the internal executable destination, and your build tools (like ghc).

  2. stack exec which my-executable will use the which command to find the full path to your executable, which you could then run, without the additional modifications that stack exec implies. If you want to be clever, you could do something like this from your shell:

    $ $(stack exec which my-executable)
    
  3. The stack install command will copy your executables into a user-specific directory, such as $HOME/.local/bin on POSIX systems. The directory will be printed to your console.

Testing

Testing is also straightforward:

1
stack test

As it happens, this is just a convenience shortcut for:

1
stack build --test

The same applies to stack bench (for benchmarking) and stack haddock (for building Haddock documentation). What this means is that you can compose these flags to build the code, build the docs, run tests, and run benchmarks:

1
stack build --test --bench --haddock

Common flags

  • --file-watch will run build in file-watch mode, where it will wait for changes to files and then automatically rebuild. This can be very convenient to run in a terminal while simulatenously editing in another window.
  • --fast will disable optimizations
  • --pedantic turns on -Wall -Werror for GHC (all warnings on, and warnings treated as errors)

So throwing a few of these together:

1
stack build --test --file-watch --fast --pedantic