Set up a Haskell Project with Stack

2020-09-14codefphaskell

In what follows we will set up a simple project using stack and see how to run code in the project using ghci, Haskell’s interactive read-eval-print loop (repl). This program will print a message on the screen. Next, we develop a program prints a random integer to the screen. This will be done using randomIO in the System.Random module. To use System.Random, we must find the package in which it is defined. We’ll do this using Hackage, a search tool for packages. The next steps are

In the appendix, we analyze the types used in the program and show how another search tool, Hoogle, can be used to find a type signature from a function name or a function name from a type signature.

Initial setup

We will run stack new randomStuff to start a new project named randomStuff. This will set up a project with the file ./app/Main.hs which looks like this:

module Main where

import Lib

main :: IO ()
main = someFunc

If we look at .src/Lib.h, we find the below:

module Lib ( someFunc ) where

someFunc :: IO ()
someFunc = putStrLn "someFunc"

Thus, running the program Main.hs should print “someFunc”. Let’s set things up now:

$ stack new randomStuff
$ stack ghci
*Main Lib> main
someFunc

Success!

Adding a package

We are going to add code to our program so that it prints out a random number. For this we will use the module System.Random. We’ll need to find the name of the package that exposes this module. There are two good ways to do this

Either way, if look closely at the fine print at the top, we find the text

random-1.2.0: Pseudo-random number generation

This is our clue. So we put the line

- random

in the file package.yaml in just the right place, like so:

dependencies:
- base >= 4.7 && < 5
- random -- ((NEW TEXT))

Then we change Main.hs so that it looks like this:

module Main where

import Lib
import System.Random

main :: IO ()
main = someFunc

newRand = randomIO :: IO Int

Thus, we have imported the module System.Random and used it to define newRand. Before proceeding further, we test to see if this works:

$ stack ghci

*Main Lib> newRand
1095906040554822953

*Main Lib> newRand
2091178867183333176

Yes! Making progress.

Note. When you change code but are still in ghci, you can type :reload (or just :r) to reload the code.

Finishing the program

We’ve tested randomIO, and it works. But we would like to fully integrate it into our program so that when we run the program, it prints a random number to the screen, with no need use stack ghci. Below is one solution.

module Main where

import System.Random

main :: IO ()
main = newRand >>= print

newRand = randomIO :: IO Int

While the code will be explained in detail in the appendix, here is a brief version: newRand has type IO Int and print has type a -> IO (), where a is “any type.” In the present context, a = Int. So we have to fit a thing of type IO Int and a thing of type Int -> IO () together to make something of type IO (), which is the type of main. That is what the “bind” operator >>= does.

Next, we reload ghci and run main:

> :reload
Ok, two modules loaded.
> main
-3910396880313191658

The last step is to compile our program and run it in the terminal:

$ stack build
...

$ ./app/Main
664397062957259411

$ ./app/Main
-1747843471900380854

When we ran stack build, stack ran ghc on ./app/Main.hs and produced the executable file ./app/Main, which we ran from the command line to produce a “random” integer.

Appendix: Discovering the Bind Operator

Let’s try to understand how we might have come to discover the bind operator >>= as a way to fit together newRand, which makes a random number, and print, which transforms values into something that can be printed. To begin, we need the type signature of print. To find it, we use Hoogle, a search tool for functions and values. Putting print into the Hoogle search box, we get

Show a => a -> IO ()

What this means is that print has type a -> IO a, where a is constrained to be of typeclass Show a. Things of typeclass Show a are printable.

At this point we have a value of type IO Int and a function of type a -> IO (), which in our context is of type Int -> IO (). We want to fit these two things together to make e value of type IO (). Suppose we had a magical operator op that would do this, so that

newRand op print :: IO ()

This is the same as having a function (op) such that

(op) newRand print :: IO ()

If the expression above has type IO () then (op) must have type

IO Int -> (Int -> IO ()) -> IO ()

This type signature is an instance of the general pattern

m a -> (a -> m ()) -> m ()

where m = IO and a = Int. And this last type signature is an instance of the still more general

m a -> (a -> m b) -> m b

Now that we know the type of the thing we need, we use Hoogle to find it. Typing m a -> (a -> m b) -> m b into the Hoogle search box, we find the function (>>=). It is called bind and it is written :

(>>=) :: forall a b . Monad m => m a -> (a -> m b) -> m b

The term Monad occurs in the typeclass part of the definition. For now it is enough to know that

Remember this little syllogism when you encounter monads.

References