Motivation and Precursor

Stack is a relatively new addition to the haskell ecosystem appearing in 2015. Before stack, everyone who developed in haskell used the cabal build tool to manage dependencies and projects, which quickly becomes untenable unless you are an expert in cabal. Hence stack was born to avoid the well known phenomena of cabal hell (here are some links for your schadenfreude: one, two, three)

An Overview of Haskell Projects in Stack

New projects in stack are created from the command line with the stack new command like so:

stack new my-awesome-project

This will create a new sub-directory called my-awesome-project for you and build out the directory structure for a typical stack project. I won’t go through everything in detail but just give you a taste of project organization. For example here is the structure of my research projects:

haskell/
├── app
│   └── Main.hs
├── bench
│   └── Baselines.hs
├── LICENSE
├── package.yaml
├── README.md
├── Setup.hs
├── src
│   ├── Run.hs
│   ├── SAT.hs
│   ├── Utils.hs
│   ├── V.hs
│   └── VProp.hs
├── stack.yaml
├── test
│   ├── Gen.hs
│   └── Spec.hs
└── vsat.cabal

app file should contain any files that are related to executables. src should include your haskell source files. test should include any test files and bench should include benchmarking files.

There are only two files that control stack’s behavior they are package.yaml and stack.yaml. When you run stack build both files are parsed, stack will resolve any missing dependencies, download the correct version of ghc, and write a my-awesome-app.cabal file. This the .cabal file is what is actually sent to haskell’s compiler ghc underneath the hood. Thus stack sits on top of cabal. Now let’s look at these two files in detail:

The stack.yaml file

The stack.yaml file for that research project looks like this:

flags: {}
packages:
# system-ghc: false
extra-deps: []

resolver: lts-10.10
build:
  library-profiling: true
  executable-profiling: true

Depending on the template used to build your project your’s might look different. I have stripped out a lot of non-essential stuff for the sake of this tutorial. The most important part of the stack.yaml file is the resolver: field. This determines which version of ghc will be downloaded and sandboxed with your project. This is important because the haskell package server hackage is littered with successful, failed or experimental phd libraries, some of which are rarely updated. So when adding many dependencies to your project it can become extremely easy to mess up the dependency environment. Stack resolves this issue by declaring a ghc version that is long-term-supported on stackage, their version of hackage, which details any dependencies and which versions of ghc are allowed for a given package. This information is what makes stack create more reproducible builds than cabal. You’ll also observe that I’ve turned on exectuable and library profiling for my project because I’m interested in its memory and timing performance. Yes it is that easy.

The package.yaml file

This is most likely the file you will be interacting with more. Here is what my package.yaml file looks like for my research project:

name:                vsat
version:             0.1.0.0
github:              "doyougnu/vsat"
license:             BSD3
author:              "Jeffrey Young"
maintainer:          "youngjef@oregonstate.edu"
copyright:           "2018 Author name here"

extra-source-files:
- README.md

# Metadata used when publishing your package
# synopsis:            Short description of your package
# category:            Web

# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description:         Please see the README on Github at <https://github.com/githubuser/my-app#readme>

dependencies:
- base >= 4.7 && < 5
- containers >= 0.5
- mtl
- foldl
- bifunctors
- QuickCheck
- deepseq
- sbv
- cassava
- bytestring
- criterion
- text
- vector

library:
  source-dirs: src

default-extensions:
  - NamedFieldPuns
  - OverloadedStrings
  - FlexibleContexts
  - DeriveFunctor
  - DeriveGeneric
  - BangPatterns
  - GeneralizedNewtypeDeriving
  - DeriveTraversable
  - FlexibleInstances
  - DeriveDataTypeable

executables:
  vsat:
    main:                Main.hs
    source-dirs:         app
    ghc-options:
    - -threaded
    - -rtsopts
    - -with-rtsopts=-N
    - -Wall
    - -fno-warn-orphans
    dependencies:
    - vsat

benchmarks:
  vsat-bench:
    main:                Baselines.hs
    source-dirs:
      - bench
      - src
    ghc-options:
    - -O2
    - -threaded
    - -rtsopts
    - -with-rtsopts=-N
    dependencies:
      - criterion


tests:
  vsat-test:
    main:                Spec.hs
    source-dirs:         test
    ghc-options:
    - -threaded
    - -rtsopts
    - -with-rtsopts=-N
    dependencies:
    - vsat

The package.yaml file is a recent addition to stack v1.6. It is a bundling of stack with the HPack utility. This file essentially describes the .cabal file but has many benefits over manually manipulating the .cabal file of your project. The first and most important is that it allows you to declare dependencies once, instead of many times. This is shown above via the top-level dependencies: tag. If we were manually configuring with the .cabal file I would be forced to repeat these dependencies for every section executables, tests, benchmarks. The second major benefit to using package.yaml and HPack is that you do not need to explicitly expose each module by hand. In this project I have five modules: VProp.hs, Run.hs, Sat.hs, Utils.hs, and V.hs. If I were manually configuring I would be forced to explicitly list these all as exposed-modules: in my .cabal file. By using HPack and the package.yaml file I can simply declare them all as exposed via this line:

library:
  source-dirs: src

and HPack will take care of the rest. To see the difference, here is the auto-generated .cabal file for this project:

-- This file has been generated from package.yaml by hpack version 0.28.2.
--
-- see: https://github.com/sol/hpack
--
-- hash: 101019861e88b49f36eb5b15812738c1cb2afd3ee9da4a87b87322d7f3f50bdc

name:           vsat
version:        0.1.0.0
description:    Please see the README on Github at <https://github.com/githubuser/my-app#readme>
homepage:       https://github.com/doyougnu/vsat#readme
bug-reports:    https://github.com/doyougnu/vsat/issues
author:         Jeffrey Young
maintainer:     youngjef@oregonstate.edu
copyright:      2018 Author name here
license:        BSD3
license-file:   LICENSE
build-type:     Simple
cabal-version:  >= 1.10
extra-source-files:
    README.md

source-repository head
  type: git
  location: https://github.com/doyougnu/vsat

library
  exposed-modules:
      Run
      SAT
      Utils
      V
      VProp
  other-modules:
      Paths_vsat
  hs-source-dirs:
      src
  default-extensions: NamedFieldPuns OverloadedStrings FlexibleContexts DeriveFunctor DeriveGeneric BangPatterns GeneralizedNewtypeDeriving DeriveTraversable FlexibleInstances DeriveDataTypeable
  build-depends:
      QuickCheck
    , base >=4.7 && <5
    , bifunctors
    , bytestring
    , cassava
    , containers >=0.5
    , criterion
    , deepseq
    , foldl
    , mtl
    , sbv
    , text
    , vector
  default-language: Haskell2010

executable vsat
  main-is: Main.hs
  other-modules:
      Paths_vsat
  hs-source-dirs:
      app
  default-extensions: NamedFieldPuns OverloadedStrings FlexibleContexts DeriveFunctor DeriveGeneric BangPatterns GeneralizedNewtypeDeriving DeriveTraversable FlexibleInstances DeriveDataTypeable
  ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -fno-warn-orphans
  build-depends:
      QuickCheck
    , base >=4.7 && <5
    , bifunctors
    , bytestring
    , cassava
    , containers >=0.5
    , criterion
    , deepseq
    , foldl
    , mtl
    , sbv
    , text
    , vector
    , vsat
  default-language: Haskell2010

test-suite vsat-test
  type: exitcode-stdio-1.0
  main-is: Spec.hs
  other-modules:
      Gen
      Paths_vsat
  hs-source-dirs:
      test
  default-extensions: NamedFieldPuns OverloadedStrings FlexibleContexts DeriveFunctor DeriveGeneric BangPatterns GeneralizedNewtypeDeriving DeriveTraversable FlexibleInstances DeriveDataTypeable
  ghc-options: -threaded -rtsopts -with-rtsopts=-N
  build-depends:
      QuickCheck
    , base >=4.7 && <5
    , bifunctors
    , bytestring
    , cassava
    , containers >=0.5
    , criterion
    , deepseq
    , foldl
    , mtl
    , sbv
    , text
    , vector
    , vsat
  default-language: Haskell2010

benchmark vsat-bench
  type: exitcode-stdio-1.0
  main-is: Baselines.hs
  other-modules:
      Run
      SAT
      Utils
      V
      VProp
      Paths_vsat
  hs-source-dirs:
      bench
      src
  default-extensions: NamedFieldPuns OverloadedStrings FlexibleContexts DeriveFunctor DeriveGeneric BangPatterns GeneralizedNewtypeDeriving DeriveTraversable FlexibleInstances DeriveDataTypeable
  ghc-options: -O2 -threaded -rtsopts -with-rtsopts=-N
  build-depends:
      QuickCheck
    , base >=4.7 && <5
    , bifunctors
    , bytestring
    , cassava
    , containers >=0.5
    , criterion
    , deepseq
    , foldl
    , mtl
    , sbv
    , text
    , vector
  default-language: Haskell2010

Other stuff

stack is a deep tool that can be used for tons of useful things. If you are looking for something I have not covered here then I suggest you check out their offical docs or go chat with them on the #haskell-stack irc channel.