Your first Stack project (build, test, dist)
Tags: haskell, stack
Published: 2020-01-30 02:21:04

01 – [VS Code for Haskell in 2020](https://dev.to/egregors/vscode-for-haskell-in-2020-5dn8)
02 – Your first Stack project (build, test, dist)

# Stack – Haskell build tool-set

Despite conventional wisdom that Haskell is an academic-first language, there exist powerful production-ready build tools. The most common and useful for my opinion tool-set is [Stack](haskellstack.org). Check [previous post](https://dev.to/egregors/vscode-for-haskell-in-2020-5dn8) out to install stack and setup SV Code for development. 


In this post, we'll try to implement a pretty simple project with Stack. 

# New Stack project

First of all, we need to init our awesome project using a `new` command: `The stack new command will create a new directory containing all the needed files to start a project correctly.`


## Init from template

Stack [new](https://docs.haskellstack.org/en/stable/GUIDE/#stack-new) command creates a project skeleton according to some template. For our tiny project, we gonna use the default simple template:


```shell
stack new addStrInts
```

Now, you should see something like:

```shell
➜  addStrInts git:(master) ✗ ls
ChangeLog.md     README.md        addStrInts.cabal package.yaml     stack.yaml
LICENSE          Setup.hs         app              src              test
```

## File structure

So, we got all the necessary files to start developing. Let's take a look at the most interesting stuff Stack created for us.


* LICENSE – license according to Stack template (BSD3 by default)
* addStrInts.cabal – project build config, we'll make a few changes here soon
* app – folder with the application entry point
* src – literally place for most of your code
* test – folder for [QuickCheck](https://hackage.haskell.org/package/QuickCheck) props test

The most important file here is `addStrInts.cabal`. This is the build configuration. Maintainer info, GHC version, dependencies, compiler options, etc. All of this could be found here. 

For now, let's just update `description`, `homepage`, `bug-reports` and `author`, `maintainer` fields. Just put your GitHub user and Name into it.

Before we a going to add some code, let's check: is our project even compiles. For this purpose run `stack setup` and `stack build` (or, if you use VS Code, just press `Stack Build` button in the bottom). If you see the output like this — the project was built successfully

```shell
/Users/egregors/Documents/GitHub/addStrInts/addStrInts.cabal was modified manually. Ignoring /Users/egregors/Documents/GitHub/addStrInts/package.yaml in favor of the cabal file.
If you want to use the package.yaml file instead of the cabal file,
then please delete the cabal file.
Building all executables for `addStrInts' once. After a successful build of all of them, only specified executables will be rebuilt.
addStrInts> build (lib + exe)
Preprocessing library for addStrInts-0.1.0.0..
Building library for addStrInts-0.1.0.0..
[1 of 2] Compiling Lib
[2 of 2] Compiling Paths_addStrInts
Preprocessing executable 'addStrInts-exe' for addStrInts-0.1.0.0..
Building executable 'addStrInts-exe' for addStrInts-0.1.0.0..
[1 of 2] Compiling Main
[2 of 2] Compiling Paths_addStrInts
Linking .stack-work/dist/x86_64-osx/Cabal-2.4.0.1/build/addStrInts-exe/addStrInts-exe ...
addStrInts> copy/register
Installing library in /Users/egregors/Documents/GitHub/addStrInts/.stack-work/install/x86_64-osx/38556cea096b692240464d8e9f5f4a8fb0069d37b484d3266c298211dcc2a5e4/8.6.5/lib/x86_64-osx-ghc-8.6.5/addStrInts-0.1.0.0-2sRMucrO0w0Df0SlAhTLCI
Installing executable addStrInts-exe in /Users/egregors/Documents/GitHub/addStrInts/.stack-work/install/x86_64-osx/38556cea096b692240464d8e9f5f4a8fb0069d37b484d3266c298211dcc2a5e4/8.6.5/bin
Registering library for addStrInts-0.1.0.0..
```

## Add Str Ints implementation

So, let's add some code! We wanna write a program which will be asking a few numbers from the user, and prints sum of them (yep, real-world Haskell app)

Let's start by making a few pure functions for typecast and calculate sum, next we'll add some tests, and finally, add one IO function for interaction with the user.

And Stack will help us with all this stuff.


### Lib's code

All our "business" code we'll put in the `StrAdd` module, so let's create file `StrAdd.hs` in the `src` directory. You may delete `Lib.hs`, we don't need it anyway. 

But now you should add the new dependency in our `addStrInts.cabal` file. Go to the `library` path, and replace `Libs` by `AddStr`. 
To avoid build errors, edit `app/Main.hs`:
```haskell
module Main where

main :: IO ()
main = undefined
```

Let's start with helpers. We will get strings as input, and we need to be sure all of the chars in the string are numbers. 

```haskell
module StrAdd where

import           Data.Char

isAllDigits :: String -> Bool
isAllDigits val = all (== True)
  $ map (\x -> x `elem` ['1', '2', '3', '4', '5', '6', '7', '8', '9']) val
```

To play around with our first function we should run the ghci session. Run `stack ghci --ghci-options StrAdd.hs` in terminal to load StrAdd.hs code and try to test `isAllDigits` function (or press `Load GHCi` if you use VS Code):

```shell
Loaded GHCi configuration from /private/var/folders/sk/jzr1583n0vs6zb05b0gb7hw40000gn/T/haskell-stack-ghci/9469312f/ghci-script
[1 of 1] Compiling StrAdd           ( StrAdd.hs, interpreted )
Ok, one module loaded.
*StrAdd> isAllDigits "1234"
True
*StrAdd> isAllDigits "4st"
False
*StrAdd> isAllDigits "123_5"
False
```

Looks good (no)! So, time to add some tests! Let's go into `test/Spec.hs`
Now this file contains stub: 
```haskell
main :: IO ()
main = putStrLn "Test suite not yet implemented"
``` in `Spec.hs`. To check test runner works properly, let's try run `stack test`. 

You should see something like:
```shell
➜  src git:(master) ✗ stack test
/Users/egregors/Documents/GitHub/addStrInts/addStrInts.cabal was modified manually. Ignoring /Users/egregors/Documents/GitHub/addStrInts/package.yaml in favor of the cabal file.
If you want to use the package.yaml file instead of the cabal file,
then please delete the cabal file.
addStrInts-0.1.0.0: unregistering (local file changes: addStrInts.cabal)
addStrInts> configure (lib + exe + test)
Configuring addStrInts-0.1.0.0...
addStrInts> build (lib + exe + test)
Preprocessing library for addStrInts-0.1.0.0..
Building library for addStrInts-0.1.0.0..
Preprocessing test suite 'addStrInts-test' for addStrInts-0.1.0.0..
Building test suite 'addStrInts-test' for addStrInts-0.1.0.0..
Preprocessing executable 'addStrInts-exe' for addStrInts-0.1.0.0..
Building executable 'addStrInts-exe' for addStrInts-0.1.0.0..
addStrInts> copy/register
Installing library in /Users/egregors/Documents/GitHub/addStrInts/.stack-work/install/x86_64-osx/38556cea096b692240464d8e9f5f4a8fb0069d37b484d3266c298211dcc2a5e4/8.6.5/lib/x86_64-osx-ghc-8.6.5/addStrInts-0.1.0.0-2sRMucrO0w0Df0SlAhTLCI
Installing executable addStrInts-exe in /Users/egregors/Documents/GitHub/addStrInts/.stack-work/install/x86_64-osx/38556cea096b692240464d8e9f5f4a8fb0069d37b484d3266c298211dcc2a5e4/8.6.5/bin
Registering library for addStrInts-0.1.0.0..
addStrInts> test (suite: addStrInts-test)
            
Progress 1/2: addStrIntsTest suite not yet implemented
                        
addStrInts> Test suite addStrInts-test passed
Completed 2 action(s). 
```

It’s time to add some tests.

### Tests

First, let's try silly way. We'll add few test functions into `Spec.hs`:
```haskell
import           StrAdd

main :: IO ()
main = do
  -- quickCheck prop_isAllDigit
  putStrLn ""
  putStrLn $ if isAllDigits "123" then "OK" else "FAIL!"
  putStrLn $ if not $ isAllDigits "4st" then "OK" else "FAIL!"
  putStrLn $ if not $ isAllDigits "123_4" then "OK" else "FAIL!"
  return ()
```

Now, just run `stack test` or press `Stack Test` button in the VS Code.

You should get smth like:
```shell
...
            
Progress 1/2: addStrInts
OK
OK
OK
                        
addStrInts> Test suite addStrInts-test passed
...

```

Looks ok, but writing test this way is too boring and inefficient. And, obviously, it's hard to cover quite a few cases. That is why in Haskell we usually write property tests.

Let's take a look at the `QuickCheck`. The main idea is that you need to describe `QuickCheck` a set of the properties your function should have, and `QuickCheck` will automatically generate a bunch of testing data for this function.   

Let's try to rewrite our test, but first, we need to add a new dependency. Go to `addStrInts.cabal`, `test-suite addStrInts-test` part and add `QuickCheck` to `build-depends` field:
```shell
...
test-suite addStrInts-test
  type: exitcode-stdio-1.0
  main-is: Spec.hs
  other-modules:
      Paths_addStrInts
  hs-source-dirs:
      test
  ghc-options: -threaded -rtsopts -with-rtsopts=-N
  build-depends:
      addStrInts
    , base >=4.7 && <5
    , QuickCheck
  default-language: Haskell2010
```

Now, we can use `QuickCheck` in our test. So, let's say if `isAllDigit` is true and it's not an empty string, then the length of the original string must be the equal string after `isDigit` filter:

```haskell
import           StrAdd
import           Data.Char
import           Test.QuickCheck

prop_isAllDigit :: String -> Bool
prop_isAllDigit val = if isAllDigits val || val == ""
  then onlyDigit == length val
  else onlyDigit /= length val
  where onlyDigit = length $ filter isDigit val

main :: IO ()
main = do
  quickCheck prop_isAllDigit
  putStrLn "Done"
```

Now, run the test `stack test`:
```shell
➜  src git:(master) ✗ stack test
/Users/egregors/Documents/GitHub/addStrInts/addStrInts.cabal was modified manually. Ignoring /Users/egregors/Documents/GitHub/addStrInts/package.yaml in favor of the cabal file.
If you want to use the package.yaml file instead of the cabal file,
then please delete the cabal file.
addStrInts> test (suite: addStrInts-test)

*** Failed! Falsified (after 1 test):  
""
Done

addStrInts> Test suite addStrInts-test passed
```

And we got the first error! Looks like `isAllDigit` do not work properly for an empty string. 

This is a quick fix (StrAdd.hs) by pattern matching:
```haskell
module StrAdd where

import           Data.Char

isAllDigits :: String -> Bool
-- isAllDigits val = all (== True) $ map isDigit val
isAllDigits ""  = False
isAllDigits val = all (== True)
  $ map (\x -> x `elem` ['1', '2', '3', '4', '5', '6', '7', '8', '9']) val

```

So `+++ OK, passed 100 tests.` looks good, but 100 cases are too few for us. Let say, to be perfectly sure, we wanna 1000 cases:

```haskell
main :: IO ()
main = do
  quickCheckWith stdArgs { maxSuccess = 1000 } prop_isAllDigit
  putStrLn "Done"
```

By `quickCheckWith` we ask `QuickCheck` to generate `maxSuccess` test cases. And dramatically fast we got another error:

```shell
*** Failed! Falsified (after 608 tests):                  
"0"
Done
```

It looks like, we forget about zero! I'm sure you noticed it right away.
Let's modify our function:
```haskell
isAllDigits :: String -> Bool
isAllDigits ""  = False
isAllDigits val = all (== True) $ map isDigit val
```

Try to run `stack test` again to be sure, all works properly now. Well done!

Now just add the remaining code, and move on to writing the IO action.
Your `StrAdd` should look like:
```haskell
module StrAdd where

import           Data.Char

isAllDigits :: String -> Bool
isAllDigits ""  = False
isAllDigits val = all (== True) $ map isDigit val

strAddInts :: String -> String -> Either String Int
strAddInts a b
  | isAllDigits a && isAllDigits b       = Right (read a + read b)
  | not (isAllDigits a || isAllDigits b) = Left "Both args are wrong"
  | not (isAllDigits a)                  = Left "First arg is wrong"
  | otherwise                            = Left "Second ars is wrong"

```


### Entry point

Nobody likes dirty function, especially in Haskell, but unfortunately without any side effects our code becomes absolutely useless (even more useless then our current project!). So, let's add some IO actions for communication with the user. 

Functions with side effects – the most dangerous and unpredictable functions in a whole project. So you need to try to make that code isolated. In this small Stack project, we'll place all user-interaction code in the program entry point `app/Main.hs`. This way, the entry point will be responsible just for interacting with the user, and all "business" code stays aside in src/StrAdd.hs. 

```haskell
module Main where

import           StrAdd

main :: IO ()
main = do
  putStrLn "Enter first argument"
  a <- getLine
  putStrLn "Enter second argument"
  b <- getLine

  let result = strAddInts a b
  
  putStrLn (displayResult result)
```

Don't forgate add `displayResult` helper function in `StrAdd.hs`:

```haskell
displayResult :: Either String Int -> String
displayResult (Left  addError) = "error: " ++ show addError
displayResult (Right res     ) = "answer: " ++ show res

```

This is all. Now we should build the whole our project: `stack build`. If you did all right build process should finish without errors.

After the build, we finally may try to run our project as a real program: `stack exec addStrInts-exe`

Looks good!

```shell
➜  addStrInts git:(master) ✗ stack exec addStrInts-exe
Enter first argument
42
Enter second argument
69
answer: 111
```

## Conclusion

According to the stack-way, you could develop Haskell applications within a full-featured powerful build system. Of cause, in this post, we touched just a tip of the iceberg, but it will be enough for the first meeting and start working with Stack. 

We’ll meet a Stack much closer next time, trying to implement `http` interaction and external dependencies