This tutorial assumes you've already installed the Stack build
tool. If you haven't yet, please start with the
get started page and then come back here.
You may also be interested in learning:
In this article we'll be talking about how to write short scripts and
self-contained programs in Haskell with Stack.
When you install Stack, the common Haskell tools like ghc
and ghci
do not end up on your PATH. This is to allow you to easily switch
between different GHC versions on projects. The easiest way to access
these tools is to prefix any command with stack exec --
, e.g.:
stack exec -- ghc --version
If you haven't installed GHC yet, you'll get an error message about
ghc
not being found. You can resolve that by running stack setup
first, or by adding the --install-ghc
option to enable automatic GHC
installation:
stack setup
# or
stack --install-ghc exec -- ghc --version
NOTE The --
after exec
tells Stack to pass the rest of the
command line arguments to the specified program instead of parsing
them itself.
Compiling an executable
Let's write a simple program, save it in HelloWorld.hs
:
main :: IO ()
main = putStrLn "Hello World!"
To compile it, you can run
$ stack exec -- ghc HelloWorld.hs
This uses the ghc
executable directly to perform the
compilation. But actually, since exec ghc
is such a common
combination, we have a convenience shortcut available:
$ stack ghc -- HelloWorld.hs
You can now run your program with ./HelloWorld
, or on Windows HelloWorld.exe
.
Running a script
We don't always want to compile an executable. Sometimes it's
convenient to just use GHC's scripting capabilities. To do that, you
can run:
$ stack exec -- runghc HelloWorld.hs
Or, as you may have guessed:
$ stack runghc -- HelloWorld.hs
Adding packages
Let's say we want to use a package that doesn't ship with GHC itself,
like http-conduit. The
stack exec
command - and its shortcuts like stack ghc
and stack runghc
- all take a --package
argument indicating that an
additional package needs to be present. So consider this http.hs
file:
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Lazy.Char8 as L8
import Network.HTTP.Simple
main :: IO ()
main = do
response <- httpLBS "https://httpbin.org/get"
putStrLn $ "The status code was: " ++
show (getResponseStatusCode response)
print $ getResponseHeader "Content-Type" response
L8.putStrLn $ getResponseBody response
You can ensure that it runs with the http-conduit
package available
by using:
$ stack runghc --package http-conduit -- http.hs
Specify a resolver
There's unfortunately a major downside to everything we've seen so
far: there are no guarantees given about which version of GHC or
libraries will be used. The selection will depend entirely on what
Stack deems the most appropriate resolver - or collection of GHC and
libraries - when you start using it. If you want to be guaranteed that
a specific set of packages will be used, you can set the --resolver
on the command line.
$ stack --resolver lts-12.21 runghc --package http-conduit http.hs
Script interpreter
Remembering to pass all of these flags on the command line is very
tedious, error prone, and makes it difficult to share scripts with
others. To address this, Stack has a
script interpreter feature
which allows these flags to be placed at the top of your script. Stack
also has a dedicated script
command which has some nice features
like auto-detection of packages you need based on import statements.
If we modify our http.hs
to say:
#!/usr/bin/env stack
-- stack --resolver lts-12.21 script
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.ByteString.Lazy.Char8 as L8
import Network.HTTP.Simple
main :: IO ()
main = do
response <- httpLBS "https://httpbin.org/get"
putStrLn $ "The status code was: " ++
show (getResponseStatusCode response)
print $ getResponseHeader "Content-Type" response
L8.putStrLn $ getResponseBody response
We can now run it by simply typing:
$ stack http.hs
Furthermore, on POSIX systems, that line at the top is known as the
shebang. This
means that, if you make your script executable, you can just run it
like a normal program:
$ chmod +x http.hs
$ ./http.hs
If you want to create self contained scripts, a script interpreter
line at the top of your file is a highly recommended practice.
What's next
The intuitions we've built for the ghc
and runghc
commands applies
equally well to the ghci
REPL. This is discussed at more length in
the How to Play tutorial. The quick summary is: stack exec --package http-conduit -- ghci
to get a powerful REPL ready to
make HTTP requests.
While the script interpreter is great for small programs, it doesn't
scale nicely to very large projects. At that point, we recommend you
bump up to a full-blown Stack project, which allows for more complex
configuration via a stack.yaml
file. You can find more details on
this in How to Build.
There's also a blog post available on writing
bitrot-free scripts with Haskell,
which heavily uses the techniques mentioned here.