Note to self: loeb and dynamic programming

Note to self: solving a dynamic programming problem using löb. Literate Haskell source for this post is here: DPloeb.lhs.

> module DPloeb where


The first solution is taken from http://blog.mno2.org/blog/2011/11/28/writing-dynamic-programming-in-haskell.

> coins :: [Int]
> coins = [1, 2, 5, 10, 20, 50, 100, 200]
>
> sol1 :: Int -> Int
> sol1 = (!!) (ways2 coins)
>     where ways1 [] = 1 : repeat 0
>           ways1 (c:cs) = n
>               where n = zipWith (+) (ways1 cs) (replicate c 0 ++ n)
>
> main1 :: IO ()
> main1 = print $sol1 200  There are 73682 ways to select coins of the denominations specified in coins that sum to 200: > *DPloeb> main1 > 73682  First step: roll out the parameter to way: > ways2 :: [Int] -> [Int] > ways2 [] = 1 : repeat 0 > > ways2 [1, 2, 5, 10, 20, 50, 100, 200] = n > where c = 1 > cs = [2, 5, 10, 20, 50, 100, 200] > n = zipWith (+) (ways2 cs) (replicate c 0 ++ n) > > ways2 [2, 5, 10, 20, 50, 100, 200] = n > where c = 2 > cs = [5, 10, 20, 50, 100, 200] > n = zipWith (+) (ways2 cs) (replicate c 0 ++ n) > > ways2 [5, 10, 20, 50, 100, 200] = n > where c = 5 > cs = [10, 20, 50, 100, 200] > n = zipWith (+) (ways2 cs) (replicate c 0 ++ n) > > ways2 [10, 20, 50, 100, 200] = n > where c = 10 > cs = [20, 50, 100, 200] > n = zipWith (+) (ways2 cs) (replicate c 0 ++ n) > > ways2 [20, 50, 100, 200] = n > where c = 20 > cs = [50, 100, 200] > n = zipWith (+) (ways2 cs) (replicate c 0 ++ n) > > ways2 [50, 100, 200] = n > where c = 50 > cs = [100, 200] > n = zipWith (+) (ways2 cs) (replicate c 0 ++ n) > > ways2 [100, 200] = n > where c = 100 > cs = [200] > n = zipWith (+) (ways2 cs) (replicate c 0 ++ n) > > ways2 [200] = n > where c = 200 > cs = [] > n = zipWith (+) (ways2 cs) (replicate c 0 ++ n) > > sol2 :: Int -> Int > sol2 = (!!) (ways2 [1,2,5,10,20,50,100,200]) > > main2 :: IO () > main2 = print$ sol2 200


Next, change the parameter from [Int] to Int by indexing on integers instead of various lists:

> ways3 :: Int -> [Int]
>
> ways3 0 = 1 : repeat 0
>
> ways3 1 = n
>     where c  = 1
>           cs = 2
>           n  = zipWith (+) (ways3 cs) (replicate c 0 ++ n)
>
> ways3 2 = n
>     where c  = 2
>           cs = 3
>           n  = zipWith (+) (ways3 cs) (replicate c 0 ++ n)
>
> ways3 3 = n
>     where c  = 5
>           cs = 4
>           n  = zipWith (+) (ways3 cs) (replicate c 0 ++ n)
>
> ways3 4 = n
>     where c  = 10
>           cs = 5
>           n  = zipWith (+) (ways3 cs) (replicate c 0 ++ n)
>
> ways3 5 = n
>     where c  = 20
>           cs = 6
>           n  = zipWith (+) (ways3 cs) (replicate c 0 ++ n)
>
> ways3 6 = n
>     where c  = 50
>           cs = 7
>           n  = zipWith (+) (ways3 cs) (replicate c 0 ++ n)
>
> ways3 7 = n
>     where c  = 100
>           cs = 8
>           n  = zipWith (+) (ways3 cs) (replicate c 0 ++ n)
>
> ways3 8 = n
>     where c  = 200
>           cs = 0
>           n  = zipWith (+) (ways3 cs) (replicate c 0 ++ n)
>
> sol3 :: Int -> Int
> sol3 x = ways3 1 !! x
>
> main3 :: IO ()
> main3 = print $sol3 200  Substitute the value of cs in the body of the where clauses: > sol4 :: [Int] > sol4 = ways1 > where > > ways0 = 1 : repeat 0 > > ways1 = n > where c = 1 > n = zipWith (+) ways2 (replicate c 0 ++ n) > > ways2 = n > where c = 2 > n = zipWith (+) ways3 (replicate c 0 ++ n) > > ways3 = n > where c = 5 > n = zipWith (+) ways4 (replicate c 0 ++ n) > > ways4 = n > where c = 10 > n = zipWith (+) ways5 (replicate c 0 ++ n) > > ways5 = n > where c = 20 > n = zipWith (+) ways6 (replicate c 0 ++ n) > > ways6 = n > where c = 50 > n = zipWith (+) ways7 (replicate c 0 ++ n) > > ways7 = n > where c = 100 > n = zipWith (+) ways8 (replicate c 0 ++ n) > > ways8 = n > where c = 200 > n = zipWith (+) ways0 (replicate c 0 ++ n) > > main4 :: IO () > main4 = print$ sol4 !! 200


Substitute the value of c in the body of the where clauses:

> sol5 :: [Int]
> sol5 = ways1
>   where
>   ways0 = 1 : repeat 0
>
>   ways1 = zipWith (+) ways2 (replicate 1   0 ++ ways1)
>   ways2 = zipWith (+) ways3 (replicate 2   0 ++ ways2)
>   ways3 = zipWith (+) ways4 (replicate 5   0 ++ ways3)
>   ways4 = zipWith (+) ways5 (replicate 10  0 ++ ways4)
>   ways5 = zipWith (+) ways6 (replicate 20  0 ++ ways5)
>   ways6 = zipWith (+) ways7 (replicate 50  0 ++ ways6)
>   ways7 = zipWith (+) ways8 (replicate 100 0 ++ ways7)
>   ways8 = zipWith (+) ways0 (replicate 200 0 ++ ways8)
>
> main5 :: IO ()
> main5 = print $sol5 !! 200  Now tweak the type and collect each of the clauses into a list: > ways6 :: [[Int]] > ways6 = let ways0 = 1 : repeat 0 > ways1 = zipWith (+) ways2 (replicate 1 0 ++ ways1) > ways2 = zipWith (+) ways3 (replicate 2 0 ++ ways2) > ways3 = zipWith (+) ways4 (replicate 5 0 ++ ways3) > ways4 = zipWith (+) ways5 (replicate 10 0 ++ ways4) > ways5 = zipWith (+) ways6 (replicate 20 0 ++ ways5) > ways6 = zipWith (+) ways7 (replicate 50 0 ++ ways6) > ways7 = zipWith (+) ways8 (replicate 100 0 ++ ways7) > ways8 = zipWith (+) ways0 (replicate 200 0 ++ ways8) > ways = [ways0, ways1, ways2, ways3, ways4, ways5, ways6, ways7, ways8] > in ways > > sol6 :: Int -> Int > sol6 x = ways6 !! 1 !! x > > main6 :: IO () > main6 = print$ sol6 200


Instead of referring to whys4, index into the list with why !! 4:

> ways7 :: [[Int]]
> ways7 = let ways0 = 1 : repeat 0
>             ways1 = zipWith (+) (ways !! 2) (replicate 1   0 ++ (ways !! 1))
>             ways2 = zipWith (+) (ways !! 3) (replicate 2   0 ++ (ways !! 2))
>             ways3 = zipWith (+) (ways !! 4) (replicate 5   0 ++ (ways !! 3))
>             ways4 = zipWith (+) (ways !! 5) (replicate 10  0 ++ (ways !! 4))
>             ways5 = zipWith (+) (ways !! 6) (replicate 20  0 ++ (ways !! 5))
>             ways6 = zipWith (+) (ways !! 7) (replicate 50  0 ++ (ways !! 6))
>             ways7 = zipWith (+) (ways !! 8) (replicate 100 0 ++ (ways !! 7))
>             ways8 = zipWith (+) (ways !! 0) (replicate 200 0 ++ (ways !! 8))
>             ways = [ways0, ways1, ways2, ways3, ways4, ways5, ways6, ways7, ways8]
>             in ways
>
> sol7 :: Int -> Int
> sol7 x = ways7 !! 1 !! x
>
> main7 :: IO ()
> main7 = print $sol7 200  Now we can define ways directly as a list: > ways8 :: [[Int]] > ways8 = let ways = [ 1 : repeat 0 > , zipWith (+) (ways !! 2) (replicate 1 0 ++ (ways !! 1)) > , zipWith (+) (ways !! 3) (replicate 2 0 ++ (ways !! 2)) > , zipWith (+) (ways !! 4) (replicate 5 0 ++ (ways !! 3)) > , zipWith (+) (ways !! 5) (replicate 10 0 ++ (ways !! 4)) > , zipWith (+) (ways !! 6) (replicate 20 0 ++ (ways !! 5)) > , zipWith (+) (ways !! 7) (replicate 50 0 ++ (ways !! 6)) > , zipWith (+) (ways !! 8) (replicate 100 0 ++ (ways !! 7)) > , zipWith (+) (ways !! 0) (replicate 200 0 ++ (ways !! 8)) > ] > in ways > > sol8 :: Int -> Int > sol8 x = ways8 !! 1 !! x > > main8 :: IO () > main8 = print$ sol8 200


Factor out whys by writing each list element as a function.

> ways9 :: [[Int]]
> ways9 = let fs   = [ const $1 : repeat 0 > , w -> zipWith (+) (w !! 2) (replicate 1 0 ++ (w !! 1)) > , w -> zipWith (+) (w !! 3) (replicate 2 0 ++ (w !! 2)) > , w -> zipWith (+) (w !! 4) (replicate 5 0 ++ (w !! 3)) > , w -> zipWith (+) (w !! 5) (replicate 10 0 ++ (w !! 4)) > , w -> zipWith (+) (w !! 6) (replicate 20 0 ++ (w !! 5)) > , w -> zipWith (+) (w !! 7) (replicate 50 0 ++ (w !! 6)) > , w -> zipWith (+) (w !! 8) (replicate 100 0 ++ (w !! 7)) > , w -> zipWith (+) (w !! 0) (replicate 200 0 ++ (w !! 8)) > ] > ways = map ($ ways) fs
>             in ways
>
> sol9 :: Int -> Int
> sol9 x = ways9 !! 1 !! x
>
> main9 :: IO ()
> main9 = print $sol9 200  Now use loeb: > loeb :: Functor f => f (f b -> b) -> f b > loeb fs = go where go = fmap ($ go) fs
>
> fs10 :: [[[Int]] -> [Int]]
> fs10   = [ const $1 : repeat 0 > , w -> zipWith (+) (w !! 2) (replicate 1 0 ++ (w !! 1)) > , w -> zipWith (+) (w !! 3) (replicate 2 0 ++ (w !! 2)) > , w -> zipWith (+) (w !! 4) (replicate 5 0 ++ (w !! 3)) > , w -> zipWith (+) (w !! 5) (replicate 10 0 ++ (w !! 4)) > , w -> zipWith (+) (w !! 6) (replicate 20 0 ++ (w !! 5)) > , w -> zipWith (+) (w !! 7) (replicate 50 0 ++ (w !! 6)) > , w -> zipWith (+) (w !! 8) (replicate 100 0 ++ (w !! 7)) > , w -> zipWith (+) (w !! 0) (replicate 200 0 ++ (w !! 8)) > ] > > sol10 :: Int -> Int > sol10 x = loeb fs10 !! 1 !! x > > main10 :: IO () > main10 = print$ sol10 200


Finally, we can generalise this solution by writing a function to produce the elements of fs10:

> make :: [Int] -> Int -> ([[Int]] -> [Int])
> make _  0 = const $1 : repeat 0 > make cs k = if k == length cs > then w -> zipWith (+) (w !! 0) (replicate (cs !! (k-1)) 0 ++ (w !! k)) > else w -> zipWith (+) (w !! (k+1)) (replicate (cs !! (k-1)) 0 ++ (w !! k)) > > sol11 :: Int -> Int > sol11 x = result !! 1 !! x > where result = loeb$ map (make coins) [0..length coins]
>
> main11 :: IO ()
> main11 = print $sol11 200  These all say 73682: > mains :: IO () > mains = do main1 > main2 > main3 > main4 > main5 > main6 > main7 > main8 > main9 > main10 > main11  Fix crackling audio in Skype on Debian Jessie On Debian Jessie my Skype audio had a horrible crackle and the microphone on my USB headset seemed to be at a very low level (barely audible on the test call). This fixed all of the audio problems: PULSE_LATENCY_MSEC=30 skype  Note to self: ffmpeg low quality. Sometimes high quality video files (e.g. mkv) don’t play on my HP Mini Netbook, so here is an ffmpeg command to rescale the video to lower quality: opts='-qscale 5 -r 25 -ar 44100 -ab 96 -s 500x374' ffmpeg -y -i infile.mkv${opts} outfile.mp4


Note to self on constructing a monad transformer. In a way this follows on from the earlier post Applicatives compose, monads do not. Literate Haskell source for this post is available here: https://github.com/carlohamalainen/playground/tree/master/haskell/transformers.

> {-# LANGUAGE ScopedTypeVariables, InstanceSigs, MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}
>
>
>
> hole = undefined
> data Hole = Hole


A Reader is a data type that encapsulates an environment. The runReader function takes an environment (a Reader) and runs it, producing the result of type a.

> newtype Reader r a = Reader { runReader :: r -> a }


Note that with record syntax, the type of runReader is actually

runReader :: Reader r a -> (r -> a)


Reader that increments its environment value, returning the same type:

> reader1 :: Num r => Reader r r
> reader1 = Reader $r -> r + 1  Reader that converts its environment value into a string: > reader2 :: Show r => Reader r String > reader2 = Reader$ r -> "reader2: " ++ (show r)


> ghci> runReader reader1 100
> 101


There’s nothing magic about runReader; it is just taking the function out of the data type. We can do it manually ourselves:

> runReader' :: Reader r a -> (r -> a)

> ghci> runReader' reader1 100
> 101


> instance Monad (Reader r) where
>     return :: a -> Reader r a
>     return a = Reader $_ -> a > > (>>=) :: forall a b. Reader r a -> (a -> Reader r b) -> Reader r b > m >>= k = Reader$ r -> runReader (k (runReader m r :: a) :: Reader r b) r


The definition of >>= is relatively easy to work out using hole driven development.

Example usage:

> eg1 :: Reader Int String
> eg1 = Reader id >>= e -> return $"hey, " ++ show e  Or in the more readable do notation: > eg1' :: Reader Int String > eg1' = do e return$ "hey, " ++ show e

> ghci> runReader eg1' 100
> "hey, 100"


Note that we use id to produce a Reader that just passes its environment argument along as the output. See readerAsk later for the equivalent situation which uses return for MonadReader.

Since [] is an instance of Monad we can also do things like this:

> eg1'' :: Reader Int String
> eg1'' = do e  [] Int)
>            return $"hey, " ++ show e  > ghci> runReader eg1'' 100 > "hey, [100]"  Reader Transformer We’d like to use the Reader in conjunction with other monads, for example running IO actions but having access to the reader’s environment. To do this we create a transformer, which we’ll call ReaderT: > newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }  The r parameter is the reader environment as before, m is the monad (for example IO), and a is the result type, as before. Again, note the actual type of runReaderT: > runReaderT :: ReaderT r m a -> (r -> m a)  It takes a ReaderT and provides us with a function that takes a reader environment of type r and produces a monadic value. Following from the Monad instance declaration for Reader it is straightforward to write the definition for ReaderT. > instance Monad m => Monad (ReaderT r m) where > return a = ReaderT$ r -> return a
>
>     (>>=) :: forall a b. ReaderT r m a -> (a -> ReaderT r m b) -> ReaderT r m b
>     m >>= k  = ReaderT $r -> do let a' = runReaderT m r :: m a > a'' runReaderT (k a'') r :: m b  With a ReaderT as the outer monad, we would like to “lift” a monadic computation into the reader. A monadic action doesn’t know anything about the reader environment, so to lift the monadic value we just create a ReaderT with a function that ignores the environment: > liftReaderT :: m a -> ReaderT r m a > liftReaderT m = ReaderT (_ -> m)  Example usage: > egLift :: ReaderT Int IO () > egLift = do e IO Int) -- This is similar to "Reader id" earlier. > liftReaderT$ print "boo"
>             liftReaderT $print$ "value of e: " ++ show e


Note the type of return on the first line of egLift. In this context, return :: a -> m a is the equivalent of id :: a -> a from the earlier Reader example.

> ghci> runReaderT egLift 100
> "boo"
> "value of e: 100"


More generally, let’s name the “ask” function:

> readerAsk :: (Monad m) => ReaderT r m r


If we want to modify the environment, we use withReaderT which takes as its first parameter a function to modify the environment. Note that the result is of type ReaderT r’ m a so the function is of type r’ -> r which modifies the supplied reader of type ReaderT r m a.

> withReaderT :: (r' -> r) -> ReaderT r m a -> ReaderT r' m a
> withReaderT f rt = ReaderT $(runReaderT rt) . f  Lastly, it is convenient to apply a function to the current environment. > readerReader :: Monad m => (r -> a) -> ReaderT r m a > readerReader f = ReaderT$ r -> return (f r)


This is almost the same as readerAsk except that we create a reader that returns f r instead of f. In other words:

> readerAsk' :: (Monad m) => ReaderT r m r


> class (Monad m) => MonadReader r m | m -> r where
>     -- Retrieve the monad environment.
>
>     -- Execute the computation in the modified environment.
>     local :: (r -> r) -> m a -> m a
>
>     -- Retrieves a function of the current environment.
>     reader :: (r -> a) -> m a


An instance declaration for our ReaderT type:

> instance Monad m => MonadReader r (ReaderT r m) where
>     local  = withReaderT  :: (r -> r) -> ReaderT r m a -> ReaderT r m a


Now we can write fairly succinct code as follows. Use the IO monad as the inner monad in a ReaderT, with an Int environment and String result type.

> eg2 :: ReaderT Int IO String
> eg2 = do
>     -- Get the reader environment.
>     e
>     -- Run an IO action; we have to use liftReaderT since we are currently
>     liftReaderT $print$ "I'm in the eg2 function and the environment is: " ++ (show e)
>
>     -- Final return value, a string.
>     return $"returned value: " ++ show e  > ghci> result "I'm in the eg2 function and the environment is: 100" > > ghci> result > "returned value: 100"  All of the above is available from Control.Monad.Reader and Control.Monad.Trans. StateT, ReaderT, and ask The State monad encapsulates a modifiable state. It has a transformer StateT as one would expect. Yet we are able to call ask inside a StateT monad. Here is an example (the raw code is here): > import Control.Monad.Reader > import Control.Monad.State > > inside0 :: ReaderT String IO Float > inside0 = do > e > liftIO$ putStrLn $"inside0, e: " ++ show e > > return 1.23 > > inside1 :: StateT [Int] (ReaderT String IO) Float > inside1 = do > e s > liftIO$ putStrLn $"inside1, e: " ++ show e > liftIO$ putStrLn $"inside1, s: " ++ show s > > put [1, 1, 1] > > return 1.23 > > run0 :: IO () > run0 = do let layer1 = runReaderT inside0 "reader environment, hi" > > result > print$ "result: " ++ show result
>
> run1 :: IO ()
> run1 = do let layer1 = runStateT inside1 [0]
>
>           (result, finalState)
>           print $"final state: " ++ show finalState > > print$ "result: " ++ show result


The function inside0 has the IO monad nested inside a Reader, while inside1 has a StateT with the ReaderT inside. Yet in both we can write e <- ask.

Inspecting the types using ghcmod-vim we find that

>   -- in inside0
>   e
>   -- in inside1


so there must be a type class that provides the ask function for StateT.

First, inspect the type of ask using ghci (here we are using the definitions from Control.Monad.Reader and Control.Monad.State, not the implementation in this file).

ghci> :t ask


ghci> :i StateT
(lots of stuff)
(lots of stuff)


> instance MonadReader r m => MonadReader r (Lazy.StateT s m) where
>     local = Lazy.mapStateT . local


The lift function comes from Monad.Trans.Class, and looking there we see:

> class MonadTrans t where
>     -- | Lift a computation from the argument monad to the constructed monad.
>     lift :: Monad m => m a -> t m a


So actually we are after the MonadTrans type class. Again looking at :i on StateT we see:

ghci> :i StateT
(lots of stuff)
-- Defined in Control.Monad.Trans.State.Lazy'
(lots of stuff)


So off we go to Control.Monad.Trans.State.Lazy where we finally get the answer:

> instance MonadTrans (StateT s) where
>     lift m = StateT $s -> do > a return (a, s)  This shows that for StateT, the lift function takes a monadic action and produces a state transformer that takes the current state, runs the action, and returns the result of the action along with the unmodified state. This makes sense in that the underlying action should not modify the state. (There are some laws that monad transformers must satisfy.) If we did not have the MonadTrans type class then we would have to embed the ask call manually: > inside1' :: StateT [Int] (ReaderT String IO) Float > inside1' = do > e do a return (a, s) > > s > liftIO$ putStrLn $"inside1, e: " ++ show e > liftIO$ putStrLn $"inside1, s: " ++ show s > > put [1, 1, 1] > > return 1.23  Obviously this is laborious and error-prone. In this case, Haskell’s type class system lets us implement a few classes so that ask, get, put, etc, can be used seamlessly no matter which monad transformer we are in. The downside is that reading Haskell code can be nontrivial. In our case we had to follow a trail through a few files to see where ask was actually implemented, and finding the right definition relied on us being able to infer the correct types of certain sub-expressions. Personally I am finding more and more that plain vim and ghci does not cut it for Haskell development, and something richer like ghcmod-vim is a real necessity. Shameless self plug: ghc-imported-from is also very useful 🙂 Archived Comments Date: 2015-07-17 16:38:33.037482 UTC Author: Marco Faustinellli Quite an interesting page. My compliments. Here’s my question to you: would you say that the ReaderT must always be the outer layer of the transformers’ stack? As a learning exercise I have implemented a transformers library (https://github.com/Muzietto/transformerz) and I am experimenting with the mutual position of ErrorT and ReaderT (which in my case are called respectively ET and RT). As you can see at row 122 of https://github.com/Muzietto/transformerz/blob/master/haskell/nilsson/Nilsson_03.hs moving ReaderT inside the monad stack causes trouble with the lifting of local (really ugly code with loss of error control functionalities). I wonder whether ReaderT is free to move, because all examples I know put it always at the outer layer. Date: 2015-07-18 10:58:58.401802 UTC Author: Carlo Hi Marco, You’re right in that it doesn’t really matter where the Reader goes, but it changes how the rest of the stack behaves as you noticed with error handling. Here’s a small example based on your file Nilsson_03.hs: module Main where import Control.Monad.Except import Control.Monad.Reader -- IO then Except then Reader: type IoExceptReader = ReaderT Int (ExceptT String IO) dostuff :: ReaderT Int (ExceptT String IO) Int dostuff = do -- Can use ask here (ReaderT): r <- ask if r == 42 -- Can also use throwError here (ExceptT) then throwError "Got 42, oh no" else return$ r + 1000


In dostuff we can use ask and throwError, and using local is easy because the Reader is at the top level:

blah :: IoExceptReader Int
blah = local (+1) dostuff


In contrast, if we put the Except at the top level then the inner computation readerThing runs in a Reader with
no Except capabilities, while the top level blah’ has Reader and Except.

-- IO then Reader then Error:

-- Can only use ask here, no ExceptT stuff, unlike dostuff.
r  ExceptT String (ReaderT Int IO) Int
thisLift = lift


Using each version:

main :: IO ()
main = do
e  putStrLn $"Error!? " ++ err Right x -> putStrLn$ "Got value: " ++ show x

e'  putStrLn $"Error!? " ++ err' Right x' -> putStrLn$ "Got value: " ++ show x'

print ()


Date: 2015-09-14 18:06:36.589062 UTC

Author: Tapio Niemelä

Cabal hell workaround

Note: cabal now includes the freeze command, i.e. cabal freeze and there is also Stack, so this post is now deprecated.

Lately I’ve been encountering cabal hell with a few of my Haskell projects, even ones that I felt were relatively small and straightforward. Building inside cabal sandboxes is a good idea, as it separates various projects from each other, but it does not solve the underlying problem. Michael Snoyman sums it up:

Simply stated, Hackage is a zoo. Anyone can upload anything at any time. If I write some code that depends on foobar version 0.1, and someone comes along and makes a breaking change in 0.1.1, my code will (generally) break. We also have the issue of different packages using different versions of the same underlying packages (e.g., baz requires foobar 0.1, and bin requires foobar 0.2), and therefore they cannot be installed side-by-side.

Periodically manually tightening package bounds is a pain.

Future versions of cabal will hopefully support some kind of “freeze” operation that will fix all of the package versions, but in the meantime we can use cabal-constraints to produce a cabal.config file to fix the versions.

To use cabal-constraints, I followed this procedure:

1. Install cabal-constraints, making sure to use the right version of Cabal:

git clone https://github.com/benarmston/cabal-constraints.git
cd cabal-constraints
rm -fr .cabal-sandbox cabal.sandbox.config dist

cabal sandbox init

--constraint "Cabal == $( cabal --version | grep 'Cabal library' | cut -f3 -d' ' )" cabal install --haddock-hyperlink-source --constraint "Cabal ==$( cabal --version | grep 'Cabal library' | cut -f3 -d' ' )"

echo 'export PATH=$PATH:'pwd/.cabal-sandbox/bin >>$HOME/.bashrc
source \$HOME/.bashrc


2. Build the Haskell project. Use the same version of cabal. If you have an old build tree and you have updated cabal/Cabal since then, you will have to build a version of cabal-constraints with that particular version of cabal. Go back to step 1.

cd my-haskell-project

rm -fr .cabal-sandbox cabal.sandbox.config dist

cabal sandbox init



3. Run cabal-constraints:

cabal-constraints dist/dist-sandbox-*/setup-config >> cabal.config


It produces this kind of output:

constraints: array == 0.4.0.1
, base == 4.6.0.1
, bytestring == 0.10.0.2
, containers == 0.5.0.0
, deepseq == 1.3.0.1
, directory == 1.2.0.1
, filepath == 1.3.0.1
, ghc-prim == 0.3.0.0
, integer-gmp == 0.5.0.0
, mmorph == 1.0.2
, mtl == 2.1.2
, old-locale == 1.0.0.5
, parsec == 3.1.5
, pipes == 3.3.0
, process == 1.1.0.2
, rts == 1.0
, text == 1.1.0.0
, time == 1.4.0.1
, transformers == 0.3.0.0
, unix == 2.6.0.1


4. For paranoia’s sake, rebuild the project to make sure that the constraints are correct:

# still in my-haskell-project

rm -fr .cabal-sandbox cabal.sandbox.config dist

cabal sandbox init