Contents

Top


More Haskell Notes and Examples

The Cabal of Cabal

Albert Y. C. Lai, trebla [at] vex [dot] net

ca·bal noun: the artifices and intrigues of a group of persons secretly united in a plot

The undocumented or obscure artifices and intrigues of Cabal. And these are just some important and useful ones!

User Guide

Cabal has a user guide! Did you know? Link

However, it still doesn't tell you everything…

Wrong Defaults

.cabal/config defaults want you to never write in Haskell! Or never read docs and never profile. Look at these lines:

-- library-profiling: False
...
-- documentation: False

This is an expensive error. One day, you will want library documentation on your own hard disk. One day, you will need profiling. When the day comes, you will have to distract yourself and reinstall a lot of packages. Reinstalling is slow or unsafe: pick one.

To correct this error, uncomment and change the lines:

library-profiling: True
...
documentation: True

(This paragraph assumes that you have hscolour.) Even this does not enable hyperlinking from docs to coloured source code. There is no config line for it. You must manually type cabal install --haddock-hyperlink-source every time, every bloody time.

OK, you have now adjusted your config, and ready to bite the bullet and reinstall. Here is the slow, safe, scorched earth policy. Do not succumb to the sunk cost fallacy.

  1. Understand my removing packages. In fact, understand the whole thing.

  2. We will be erasing all user packages. Slow, safe, scorched earth policy. Do not succumb to the sunk cost fallacy.

  3. Therefore, before erasing, do jot down which packages not already from GHC and Haskell Platform (if you have it) that you will want back after erasing.

    Do not jot down packages from GHC and Haskell Platform installers—they already come with docs, profiling, dynamic linking, everything. Only cabal-install disables everything, like it hates Haskell programmers.

  4. Erase: erase the appropriate metadata directory $HOME/.ghc/arch-ghcversion

  5. Install back: Use cabal install to install back the packages you jotted down. Do not use multiple commands. Use one single command for the whole roster, so that cabal-install can compute one single coherent set. Example:

    cabal install --haddock-hyperlink-source lens netwire hxt hxt-http
    

    (If you don't have hscolour, --haddock-hyperlink-source does not apply.)

Lock Down Library Versions

Co-existing multiple versions or instances of a library is problematic, see my the pigeon-drop con. There is no such thing as “upgrade”; there is only reinstall and install more.

To guard against multiple versions or instances of bytestring and containers (say) during cabal install, add these flags:

--constraint "bytestring installed" --constraint "containers installed"

or insert these lines into $HOME/.cabal/config:

constraint: bytestring installed
constraint: containers installed

A complete list for Haskell Platform is at my Haskell Platform article.

Moar Optimization

.cabal/config defaults optimization to

-- optimization: True

This means passing -O to GHC. Do you want -O2 instead? Without command line options? You can change the config line to:

optimization: 2

That's right, it does not have to be boolean! The complete list of valid values are: False, false (will cause warning), 0, True, true (will cause warning), 1, 2.

Haskell 2010

Cabal normally tells GHC to use Haskell 98. This is why some files are accepted when using GHC by hand but rejected when using Cabal. To correct this error: In project.cabal files, under each library section and executable section, add this undocumented field

default-language: Haskell2010

As a result of using this setting, it is also a good idea to request Cabal version 1.10 or higher in the global (top) section:

cabal-version: >=1.10

So here is how it goes in a sample project.cabal file:

name: dummy
version: 1.1
cabal-version: >= 1.10
build-type: Simple
author: Albert Y. C. Lai
category: Dummy
synopsis: Example executable and example library.

executable dummy
  main-is: dummy.hs
  build-depends: base >= 4
  default-language: Haskell2010

library
  exposed-modules: Dummy
  build-depends: base >= 4
  default-language: Haskell2010

Its significance is exemplified by the following file, which uses Haskell 2010 new features left right and centre. As said, it is normally accepted by GHC, but normally rejected via Cabal, unless you add the above line.

import Data.Set(empty, minView)

-- foreign declarations
foreign import ccall abort :: IO ()

-- pattern guards
f s | Just (x, s1) <- minView s = x
    | Nothing <- minView s = "empty"

-- do and if-then-else
main = do
  if False
  then abort
  else putStrLn (f empty)

And just what more undocumented valid fields can you use in project.cabal files? This brings us to…

project.cabal Fields

To find out more undocumented valid fields in project.cabal, deliberately add an invalid field (and value) under the desired section, then run cabal configure.

Compound Constraints

Version constraints can mix && (higher precedence) and || (lower precedence), and also use parentheses to override precedences. Example:

base  ==10.*  ||  <9 && (<3 || >4)  ||  ==12.*

Local Repository

You can host additional package repositories on your file system! And yes that's plural, you can have more than one.

Let's say one repository is at /home/trebla/must-have, and it has these packages: doggie 1.0, kitteh 1.1, kitteh 1.2. Here is the expected file system layout in /home/trebla/must-have:

And here is the line in .cabal/config to use that repository:

local-repo: /home/trebla/must-have

You can use many local repositories. Just add more local-repo lines.

cabal-install will add the file 00-index.cache to the repositories for its own use. You do not have to worry about maintaining it. Just keep your 00-index.tar up to date, and cabal-install will do the right thing.

You do not need to run cabal update for changes in local repositories.

Remote Repository

You can also host and/or use additional remote package repositories! And yes that's also plural, you can have more than one.

Let's say one repository is at http://aminal-libs.com/kittehdoggie/, and it has these packages: doggie 1.0, kitteh 1.1, kitteh 1.2. Here are the expected relative paths of files:

And here is the line in .cabal/config to use that repository. First you have to choose a name; it is up to you and it just has to be unique. Let's say you choose “catdog”. Then the line is:

remote-repo: catdog:http://aminal-libs.com/kittehdoggie

You can use many remote repositories. Just add more remote-repo lines and give them names. (In fact, you already have one for Hackage.) And they don't have to be http://, they can also be file://.

You need to run cabal update for remote repositories. This downloads and caches 00-index.tar.gz. Explore .cabal/packages to see what is cached there.

cabal install ≠ Setup install

Many people use this idiom in a source tree to build and install:

cabal configure
cabal build
cabal install

This idiom is an urban legend, i.e., a popular error. The 3rd step cabal install re-does the 1st and 2nd steps, among other things. Redundant work looks harmless so far, until one day you need non-default options:

cabal configure --prefix=/nondefault --flags=debug
cabal build
cabal install

Whee! Your laborously entered options are lost on deaf ears. The 3rd step cabal install without options re-does cabal configure without options. Your options are erased.

The correct idiom is:

cabal configure --prefix=/nondefault --flags=debug
cabal build
[cabal haddock [--hyperlink-source]]
cabal copy
cabal register

or simply

cabal install --prefix=/nondefault --flags=debug [--enable-documentation [--haddock-hyperlink-source]]

This concludes the practical advice. But the story doesn't end here.

The urban legend probably arises from this analogy:

MakefileSetup.hs
./configure
make
make install
Setup configure
Setup build
Setup install

And you would think that it would carry over to cabal, no? No:

Don't you love gratuitous inconsistencies?

So why did they choose the name cabal install for the all-in-one command? Probably because of another analogy:

DebianCabal
apt-get update
apt-get install that-package
apt-get upgrade
cabal update
cabal install that-package
cabal upgrade (deprecated)

The cabal program tries too hard to use the Makefile analogy and the Debian analogy together. Analogies are like drugs.

Shared Libraries, Dynamically Linked Libraries

Cabal's default of whether to build dynamic libraries is a very complicated story depending on Cabal version, GHC version, and OS.

Cabal ≤ 1.16Cabal ≥ 1.18
GHC ≤ 7.6FalseFalse
GHC ≥ 7.8False
LinuxTrue
Mac OS XTrue
WindowsFalse

This is because:

The full story is at link.

This complicated story is clearly a transitional pain only. Eventually, no one will use Cabal ≤ 1.16 anymore (already, many people are at 1.20); eventually, no one will use Windows anymore the Windows version will enable dynamic libraries too. When that day comes, no one will run into any problem.

The transitional pain will come to pass. Meanwhile, it can show up as mysterious linker “cannot find” error messages, even when you don't use ghci or Template Haskell. Here is how:

  1. Install GHC 7.8 alone (i.e., not as part of Haskell Platform).

  2. Still using cabal-install 1.16, install some libraries directly or indirectly. This causes libraries to be installed without dynamic versions.

    The most treacherous way to install some libraries indirectly, behind your back:

    cabal install cabal-install
    

    This brings in: HTTP, mtl, network, parsec, random, stm, text, zlib, a 2nd version of Cabal (the library part), and a 2nd version of transformer.

  3. Then using a newer cabal-install, install some libraries that depend on those installed above. For example install async now, which depends on stm above.

    The newer cabal-install tries to build a dynamic version of async. This requires linking against a dynamic version of stm. But a dynamic version of stm is absent.

    /usr/bin/ld: cannot find -lHSstm-2.4.3-ghc7.8.3
    
  4. Or, when you use ghci and try to use a library:

    Prelude> import Control.Concurrent.STM
    
    <no location info>:
        Could not find module ‘Control.Concurrent.STM’
        Perhaps you haven't installed the "dyn" libraries for package ‘stm-2.4.3’?
        Use -v to see a list of the files searched for.
    

You have to rebuild those libraries that don't have dynamic versions.

An ounce of prevention is better than a pound of cure, for those who learn from history. Here are some safe practices that have averted problems in the past, avert this problem today, and will avert more problems in the future:


I have more Haskell Notes and Examples