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 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
Reference the package for
System.Random
inpackage.yaml
.Use
ghci
to testrandomIO
.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.
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
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
- 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
- There is a bind function whenever
m
is a monad. IO
is a monad- Therefore there is a bind function
IO a -> (a -> IO b) -> IO b
.
Remember this little syllogism when you encounter monads.