Set up a Haskell Project with Stack
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
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
Reference the package for
Modify the program so that it prints out a random integer.
Compile the program, again using stack.
Run the program from the command line.
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.
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
print “someFunc”. Let’s set things up now:
$ stack new randomStuff $ stack ghci *Main Lib> main someFunc
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
need to find the name of the package that
exposes this module. There are two good ways to
Google “haskell System.Random”. You will find this page.
Go to Hackage and type “System.Random” in the search box.
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
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
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
you can type
:reload (or just
:r) to reload the code.
Finishing the program
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
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
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
IO (), which is the type of
main. That is what the “bind” operator
Next, we reload
ghci and run
> :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
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
which makes a random number, and
Show a => a -> IO ()
What this means is that
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
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 ()
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.
m a -> (a -> m b) -> m b into the Hoogle search box, we find
(>>=). It is called bind and it is written
(>>=) :: forall a b . Monad m => m a -> (a -> m b) -> m b
Monad occurs in the typeclass part of the definition.
For now it is enough to know that
- There is a bind function whenever
mis a monad.
IOis a monad
- Therefore there is a bind function
IO a -> (a -> IO b) -> IO b.
Remember this little syllogism when you encounter monads.