ghcup-hs/docs/steps.md

9.0 KiB

First steps

In this guide we'll take a look at a few core tools that are installed with the Haskell toolchain, namely, ghc, runghc and ghci. These tools can be used to compile, interpret or explore Haskell programs.

First, let's start by opening your system's command line interface and running ghc --version to make sure we have successfully installed a Haskell toolchain:

➜ ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.10.7

If this fails, consult the Getting started page for information on how to install Haskell on your computer.

This guide is partly based on Gil Mizrahi's blog.

Compiling programs with ghc

Running ghc invokes the Glasgow Haskell Compiler (GHC), and can be used to compile Haskell modules and programs into native executables and libraries.

Create a new Haskell source file named hello.hs, and write the following code in it:

main = putStrLn "Hello, Haskell!"

Now, we can compile the program by invoking ghc with the file name:

➜ ghc hello.hs
[1 of 1] Compiling Main             ( hello.hs, hello.o )
Linking hello ...

For more in-depth information about the files ghc produces, follow the GHC user guide guide.

Now we run our program:

➜ ./hello 
Hello, Haskell!

Alternatively, we can skip the compilation phase by using the command runghc:

➜ runghc hello.hs
Hello, Haskell!

runghc interprets the source file instead of compiling it and does not create build artifacts. This makes it very useful when developing programs and can help accelerate the feedback loop. More information about runghc can be found in the GHC user guide.

Turning on warnings

The -Wall flag will enable GHC to emit warnings about our code.

➜ ghc -Wall hello.hs -fforce-recomp
[1 of 1] Compiling Main             ( hello.hs, hello.o )

hello.hs:1:1: warning: [-Wmissing-signatures]
    Top-level binding with no type signature: main :: IO ()
  |
1 | main = putStrLn "Hello, Haskell!"
  | ^^^^
Linking hello ...

While Haskell can infer the types of most expressions, it is recommended that top-level definitions are annotated with their types.

Now our hello.hs source file should looks like this:

main :: IO ()
main = putStrLn "Hello, world!"

And now GHC will compile hello.hs without warnings.

An interactive environment

GHC provides an interactive environment in a form of a Read-Evaluate-Print Loop (REPL) called GHCi. To enter the environment run the program ghci.

➜ ghci
GHCi, version 9.0.2: https://www.haskell.org/ghc/  :? for help
ghci> 

It provides an interactive prompt where Haskell expressions can be written and evaluated.

For example:

ghci> 1 + 1
2
ghci> putStrLn "Hello, world!"
Hello, world!

We can define new names:

ghci> double x = x + x
ghci> double 2
4

We can write multi-line code by surrounding it with :{ and :}:

ghci> :{
| map f list =
|     case list of
|         [] -> []
|         x : xs -> f x : map f xs
| :}
ghci> map (+1) [1, 2, 3]
[2,3,4]

We can import Haskell source files using the :load command (:l for short):

ghci> :load hello.hs
[1 of 1] Compiling Main             ( hello.hs, interpreted )
Ok, one module loaded.
ghci> main
Hello, Haskell!

As well as import library modules:

ghci> import Data.Bits
ghci> shiftL 32 1
64
ghci> clearBit 33 0
32

We can even ask what the type of an expression is using the :type command (:t for short):

λ> :type putStrLn
putStrLn :: String -> IO ()

To exit ghci, use the :quit command (or :q for short)

ghci> :quit
Leaving GHCi.

A more thorough introduction to GHCi can be found in the GHC user guide.

Using external packages in ghci

By default, GHCi can only load and use packages that are included with the GHC installation.

However, users of the cabal-install and stack build tools can download and load external packages very easily using the following commands:

cabal-install:

cabal repl --build-depends async,say

Stack:

stack exec --package async --package say -- ghci

And the modules of the relevant packages will be available for import:

GHCi, version 9.0.1: https://www.haskell.org/ghc/  :? for help
ghci> import Control.Concurrent.Async 
ghci> import Say
ghci> concurrently_ (sayString "Hello") (sayString "World")
Hello
World

Stack users can also use this feature with runghc and ghc by replacing ghci in the command above, and cabal-install users can generate an environment file that will make async and say visible for GHC tools in the current directory using this command:

cabal install --lib async say --package-env .

Many more packages are waiting for you on Hackage.

Creating a proper package with modules

The previous methods to compile Haskell code are for quick experiments and small programs. Usually in Haskell, we create cabal projects, where build tools such as cabal-install or stack will install necessary dependencies and compile modules in correct order. For simplicity's sake, this section will only use cabal-install.

To get started, run:

mkdir haskell-project
cd haskell-project
cabal init --interactive

If you let it generate a simple project with sensible defaults, then you should have these files:

  • src/MyLib.hs: the library module of your project
  • app/Main.hs: the entry point of your project
  • haskell-project.cabal: the "cabal" file, describing your project, its dependencies and how it's built

To build the project, run:

cabal build

To run the main executable, run:

➜ cabal run
Hello, Haskell!
someFunc

Adding dependencies

Now let's add a dependency and adjust our library module. Open haskell-project.cabal and find the library section:

library
    exposed-modules:  MyLib

    -- Modules included in this library but not exported.
    -- other-modules:

    -- LANGUAGE extensions used by modules in this package.
    -- other-extensions:
    build-depends:    base ^>=4.14.3.0
    hs-source-dirs:   src
    default-language: Haskell2010

The interesting parts here are exposed-modules and build-depends. To add a dependency, it should look like this:

    build-depends:    base ^>=4.14.3.0
	                , directory

Now open src/MyLib.hs and change it to:

module MyLib (someFunc) where

import System.Directory

someFunc :: IO ()
someFunc = do
  contents <- listDirectory "src"
  putStrLn (show contents)

Adding modules

To add a module to your package, adjust exposed-modules, like so

    exposed-modules:  MyLib
	                  OtherLib

then create src/OtherLib.hs with the following contents:

module OtherLib where

otherFunc :: String -> Int
otherFunc str = length str

To use this function interactively, we can run:

➜ cabal repl
ghci> import OtherLib
ghci> otherFunc "Hello Haskell"
13

For further information about how to manage Haskell projects see the Cabal user guide.

Where to go from here

How to learn Haskell proper

To learn Haskell, try any of those:

Projects to contribute to