The Haskell Development Environment
GHC, Cabal, and Stack
In this chapter, we’re going to learn about the main tools that you’ll be using for developing programs in Haskell. The three components we’ll be discussing are the Glasgow Haskell Compiler, otherwise known as GHC, Cabal, and Stack.
Haskell is a compiled language. If you’ve never used a compiled language, all compiled means is that before we can run our program, we have to use a translator that converts the code we write into machine code. In a non-compiled language, like Javascript, the code is just executed straight from the source code by an interpreter. In Haskell, we must first compile our code before we can run it.
The most widely used compiler for Haskell and the only one you need to care about is the Glasgow Haskell Compiler, AKA “GHC”.
Cabal is simply a system and specification for building and packaging Haskell libraries and programs. Specifically, Cabal describes what a Haskell package is, how these packages interact with the language.
When you start a Haskell project, you will create a cabal file that describes how to build your application, it’s dependencies and more. We’ll look at the format of that file a little bit later.
Stack is a relatively recent addition to the Haskell ecosystem. Stack will automatically install the right version of GHC for you in a way that is isolated so you could have different projects on the same machine that use different versions of the compiler if need be.
With Stack, you can also install packages needed for your project, build your project, and test it. On a Mac, you can install Stack with simply brew install haskell-stack
. For other operating systems, see haskellstack.org for easy instructions.
Creating and running an application
Next, we’re going to look at how to create a new project and run it using stack. The first command we’ll look at is stack templates
which displays a list of templates that we can use to create a new project. To create a new project, we use the stack new
command with the name of our new project and optionally a template. We’ll use the template named “new-template”.
stack new helloworld new-template
Using the “new-template” template, this creates a new project named helloworld and puts it in a new folder by the same name.
➜ ~ $ cd helloworld ➜ helloworld $ tree . ├── LICENSE ├── Setup.hs ├── app │ └── Main.hs ├── helloworld.cabal ├── src │ └── Lib.hs ├── stack.yaml └── test └── Spec.hs 3 directories, 7 files
Now let’s take a look at the project that was created. I’ll use atom, but you can use whatever editor you like. For reasons that will be explained in the notes accompanying this chapter, I’m going to use the stack exec
command to open the atom editor as well, but you should feel free to ignore this detail.
Stack uses a file in the project called stack.yaml. This file refers to a “resolver” A resolver simply specifies both the version of GHC you want to use and a pre-curated set of specific versions of Haskell packages that are available for you to use and build your project against.
There’s also a cabal file which describes some metadata about our package, what kind of package this is, what our dependencies are, and more. This cabal file says that our project has a library in the src directory and an executable in the app directory. The app directory has a single source file called Main.hs and the src directory has a source file called Lib.hs. Hs is obviously short for Haskell and is the file extension for Haskell source files.
I don't expect you to understand the syntax yet, but the lib source file has a single function in it which does some IO. In this case, it prints “somefunc” to the console.
module Lib ( someFunc ) where someFunc :: IO () someFunc = putStrLn "someFunc"
In main.hs
, there’s a single function called main, which is a special function just like in many other languages. It’s the point of entry into your application. When you run it, everything starts at main. This file also imports the Lib module and uses the “someFunc” function from it. As you can predict, if we run this app, it’ll print “someFunc”.
Before we can try running this, we need to make sure we actually have a usable compiler installed. I mentioned stack will help with this, but stack doesn’t assume that you want it to download and install the GHC compiler for you so you have to tell it to do so with stack setup. But if you miss this step, stack will let you know later.
Let’s run stack setup. I’ve done this step before so it finishes right away. The first time you run it, it may take a while. So now that we have the compiler installed and project created, we can build it with the stack build
command.
Now that that’s built, we can run it with the stack exec
command. Stack exec and the name of the executable, in this case helloworld-exe.
stack exec helloworld-exe
and it prints “someFunc”
GHC’s Interactive Mode
Next, we’re going to look at GHCi which is the REPL that comes with GHC. If you haven’t used a REPL before, it stands for READ-EVAL-PRINT loop and it is simply a shell or prompt which takes expressions you type, evaluates them, and prints the results for you too see. GHCi just stands for GHC Interactive and is an environment in which Haskell expressions can be interactively evaluated and programs can be interpreted.
We can start GHCi with the stack exec command, stack exec ghci
. Once GHCi is loaded, we can type in any Haskell expression and see it evaluated. Let’s try some simple ones:
Prelude> 1 + 1 2 Prelude> 2 + 2 4 Prelude> "hello" ++ " " ++ "world" "hello world"
We can also load a Haskell source file into GHCi so that we can evaluate any expressions contained in it. Let’s create a new file HelloWorld.hs
vim HelloWorld.hs
We’ll declare a single variable “x” with the value 1.
We’ll be going over this in detail in the lext chapter, but one thing to note is that the equals sign here is not the assignment operator you may be used to from other languages. In other words, x isn’t some box we can just assign anything to and here we’re assigning the value 1. Instead this syntax means that x is defined to be 1. The definition of x is 1 and it will always be 1. So x is just a name for a value.
In GHCi we can load up this source file now with :load HelloWorld.hs
. Now that’s it’s loaded, we can just enter the name the value we declared and see it interpreted.
Prelude> x 1
If we edit our source file, we can have GHCi reload the file, making our changes available to us in GHCi. Let’s go ahead and add a function called greeting. greeting will take a single String argument, name
and return a string greeting. So greeting name = “hello” ++ “ “ ++ name
Once again, we’ll be going over this syntax, but for now, all you need to know is that greeting is the name of a function that takes a single String argument name
and returns a greeting in the form of “hello” plus that name.
What if we wanted to declare a variable directly in GHCi instead of typing it in our source file and loading it up? GHCi lets us do that, but with one caveat. For reasons we won’t go into, we have to use something called a let-expression. Whereas in a Haskell source file, we can just pick a name, like x
and declare “x equals whatever”, in GHCi, we have to precede that with let
.
So if we wanted to declare a variable y with the value 42, we’d write. let y = 42
. That variable is now in scope and available in GHCi which we can see by typing it and pressing enter.
Let’s define a function that adds two numbers. We’ll add it to our source file and then load it up in GHCi and try it out.
add a b = a + b
Now we’ll go back to GHCi and reload the file. We can type :r
which is short for "reload".
Let’s try our function.
Prelude> add 2 2 4
And GHCi evaluates the expression.
If we wanted to define this directly in GHCi, it would be identical except that we need to precede it with “let”.
let add a b = a + b
In this chapter we’ve learned how to use the main tools for developing Haskell applications. We’ve seen how to create, build, and run a project and we’ve taken a quick tour of GHC’s interactive mode which allows us to evaluate expressions we type at the console and also from files.