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 --versionIf 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 --versionNOTE The -- after exec tells Stack to pass the rest of the
command line arguments to the specified program instead of parsing
them itself.
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.hsThis 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.hsYou can now run your program with ./HelloWorld, or on Windows HelloWorld.exe.
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.hsOr, as you may have guessed:
$ stack runghc -- HelloWorld.hsLet’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 responseYou can ensure that it runs with the http-conduit package available
by using:
$ stack runghc --package http-conduit -- http.hsThere’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.hsRemembering 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 responseWe can now run it by simply typing:
$ stack http.hsFurthermore, 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.hsIf you want to create self contained scripts, a script interpreter line at the top of your file is a highly recommended practice.
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.
  
    Subscribe to our blog via email
  
  
  
  Email subscriptions come from our Atom feed and are handled by Blogtrottr. You will only receive notifications of blog posts, and can unsubscribe any time.