Writing

I will use VSCode in CS 132, and the instructions below describe how to connect VSCode to the Haskell toolchain (and what to do when things go wrong).

If you prefer a different editor, the Haskell website has some documentation for using other editors to write Haskell!

1 Install the VSCode extension

Install the Haskell extension for VSCode. Installing this extension will also automatically install the Haskell Syntax Highlighting extension.

2 Download sample project

If you skipped cloning the sample project on the previous page, take a moment to clone or download it now. (If you already have the sample project, move on to the next step.)

git clone https://github.com/hmc-cs132/try-haskell

Cloning into ‘try-haskell’…
remote: Enumerating objects: 22, done.
remote: Counting objects: 100% (22/22), done.
remote: Compressing objects: 100% (15/15), done.
remote: Total 22 (delta 1), reused 22 (delta 1), pack-reused 0 (from 0)
Receiving objects: 100% (22/22), 4.91 KiB | 4.91 MiB/s, done.
Resolving deltas: 100% (1/1), done.

This project is configured to use VSCode for Haskell, once VSCode has been properly set up.

3 Check that everything works

Follow these steps to check that VSCode + Haskell is working correctly. If something goes wrong, see When things go wrong.

  1. Use VSCode to open the entire folder that contains the sample project.
  2. In the project, open the file app/Main.hs. If a window pops up to ask you about HLS, choose the option that says “Automatically via GHCup”.
  3. Wait a bit for HLS to analyze the file.
  4. Right click on the call to someFunc on line 8. Choose “Go to Definition”. If the file “src/Lib.hs” opens and you see the definition of someFunc, then everything is working!

4 Change some code

To practice writing Haskell code, and to make sure that VSCode works with Haskell, we will define, call and test a factorial function.

  1. Replace the contents of src/Lib.hs with a definition of a factorial function.
src/Lib.hs
module Lib
    ( fact
    ) where

-- | The factorial function
fact :: Integer -> Integer
fact 0 = 1
fact n = n * fact (n - 1)
  1. Open app/Main.hs. Replace its contents with a call to fact.
app/Main.hs
module Main
    ( main
    ) where

import Lib (fact)

main :: IO ()
main = print (fact 5)
  1. Open a terminal in VSCode and run stack run. The program should (eventually, after building) print 120.
  2. Open test/Spec.hs and replace its contents with the following.
test/Spec.hs
{-# OPTIONS_GHC -F -pgmF hspec-discover #-}

This strange notation allows the Haskell toolchain to automatically discover tests to run.

  1. Create a new file test/FactorialSpec.hs with the following contents.
Note

You might notice that VSCode automatically changes the format of your code when you save the file. That is by design! See the section on style.

test/FactorialSpec.hs
module FactorialSpec ( spec ) where

import Lib (fact)

import Test.Hspec (Spec, describe, shouldBe, specify)

{- Testing fact -}
spec :: Spec
spec =
    describe "fact" $  do

        specify "fact 0 = 1" $
            fact 0 `shouldBe` 1

        specify "fact 1 = 1" $
            fact 1 `shouldBe` 1

        specify "fact 3 = 6" $
            fact 3 `shouldBe` 6

        specify "fact 5 = 120" $
            fact 5 `shouldBe` 120
  1. Open a terminal in VSCode and run stack test. You should see the following output.

try-haskell> test (suite: try-haskell-test)
 

Factorial
  fact
    fact 0 = 1 [✔]
    fact 1 = 1 [✔]
    fact 3 = 6 [✔]
    fact 5 = 120 [✔]

Finished in 0.0005 seconds
4 examples, 0 failures



try-haskell> Test suite try-haskell-test passed
Completed 2 action(s).

When things go wrong

The Haskell language tools can get easily confused in VSCode. If you encounter an error that you think is incorrect or any other problems using Haskell in VSCode (for example, VSCode cannot find the definitions of functions), try these steps.

  1. Close all the files.
  2. Open a terminal in VSCode and run
stack purge
  1. Use VSCode’s command pallette to run the command “Restart Haskell LSP server”.
  2. Reopen the files. I have found better success opening files that do not import other project files first, then opening files that import other files in the project.