How to Write Comments in Haskell
Comments are often a simple item to learn, but there's a few ways we can get more sophisticated with them! This article is all about writing comments in Haskell. Here's a quick outline to get you started!
- What is a Comment?
- Single Line Comments
- Multi-Line Comments
- Inline Comments
- Writing Formal Documentation Comments
- Intro to Haddock
- Basic Haddock Comments
- Creating Our Haskell Report
- Documenting the Module Header
- Module Header Fields
- Haddock Comments Below
- Commenting Type Signatures
- Commenting Constructors
- Commenting Record Fields
- Commenting Class Definitions
A Complete Introduction to the Haskell Programming Language
What is a Comment?
A comment is non-code note you write in a code file. You write it to explain what the code does or how it works, in order to help someone else reading it. Comments are ignored by a language's compiler or interpreter. There is usually some kind of syntax to comments to distinguish them from code. Writing comments in Haskell isn't much different from other programming languages. But in this article, we'll look extensively at Haddock, a more advanced program for writing nice-looking documentation.
Single Line Comments
The basic syntax for comments in Haskell is easy, even if it is unusual compared to more common programming languages. In languages like Java, Javascript and C++, you use two forward slashes to start a single line comment:
int main() { // This line will print the string value "Hello, World!" to the console std::cerr << "Hello, World!" << std::endl; }
But in Haskell, single line comments start with two hyphens, '--':
-- This is our 'main' function, which will print a string value to the console main :: IO () main = putStrLn "Hello World!"
You can have these take up an entire line by themselves, or you can add a comment after a line of code. In this simple "Hello World" program, we place a comment at the end of the first line of code, giving instructions on what would need to happen if you extended the program.
main :: IO () main = -- Add 'do' to this line if you add another 'putStrLn' statement! putStrLn "Hello World!"
Multi-Line Comments
While you can always start multiple consecutive lines with whatever a comment line starts with in your language, many languages also have a specific way to make multiline comments. And generally speaking, this method has a "start" and an "end" sequence. For example, in C++ or Java, you start a multi line comment block with the characters '/' and end it with '/'
/* This function returns a new list that is a reversed copy of the input. It iterates through each value in the input and uses 'push_front' on the new copy. */ std::list<int> reverseList(const std::list<int>& ints) { std::list<int> result; for (const auto& i : ints) { result.push_front(i); } return result; }
In Haskell, it is very similar. You use the brace and a hyphen character to open ('{-') and then the reverse to close the block ('-}').
{- This function returns a new list that is a reversed copy of the input. It uses a tail recursive helper function. -} reverse :: [a] -> [a] reverse = reverseTail [] where reverseTail acc [] = acc reverseTail acc (x : xs) = reverseTail (x : acc) xs
Notice we don't have to start every line in the comment with double hyphens. Everything in there is part of the comment, until we reach the closing character sequence. Comments like these with multiple lines are also known as "block comments". They are useful because it is easy to add more information to the comment without adding any more formatting.
Inline Comments
While you generally use the brace/hyphen sequence to write a multiline comment, this format is surprisingly also useful for a particular form of single line comments. You can write an "inline" comment, where the content is in between operational code on that line.
reverse :: [a] -> [a] reverse = reverseTail [] where reverseTail {- Base Case -} acc [] = acc reverseTail {- Recursive Case -} acc (x : xs) = reverseTail (x : acc) xs
The fact that our code has a start and end sequence means that the compiler knows where the real code starts up again. This is impossible when you use double hyphens to signify a comment.
Writing Formal Documentation Comments
If the only people using this code will be you or a small team, the two above techniques are all you really need. They tell people looking at your source code (including your future self) why you have written things in a certain way, and how they should work. However, if other people will be using your code as a library without necessarily looking at the source code, there's a much deeper area you can explore. In these cases, you will want to write formal documentation comments. A documentation comment tells someone what a function does, generally without going into the details of how it works. More importantly, documentation comments are usually compiled into a format for someone to look at outside of the source code. These sorts of comments are aimed at people using your code as a library. They'll import your module into their own programs, rather than modifying it themselves. You need to answer questions they'll have like "How do I use this feature?", or "What argument do I need to provide for this function to work"? You should also consider having examples in this kind of documentation, since these can explain your library much better than plain statements. A simple code snippet often provides way more clarification than a long document of function descriptions.
Intro to Haddock
As I mentioned above, formal documentation needs to be compiled into a format that is more readable than source code. In most cases, this requires an additional tool. Doxygen, for example, is one tool that supports many programming languages, like C++ and Python. Haskell has a special tool called Haddock. Luckily, you probably don't need to go through any additional effort to install Haddock. If you used GHCup to install Haskell, then Haddock comes along with it automatically. (For a full walkthrough on getting Haskell installed, you can read our Startup Guide). It also integrates well with Haskell's package tools, Stack and Cabal. In this article we'll use it through Stack. So if you want to follow along, you should create a new Haskell project on your machine with Stack, calling it 'HaddockTest'. Then build the code before we add comments so you don't have to wait for it later:
>> stack new HaddockTest >> cd HaddockTest >> stack build
You can write all the code from the rest of the article in the file 'src/Lib.hs', which Stack creates by default.
Basic Haddock Comments
Now let's see how easy it is to write Haddock comments! To write basic comments, you just have to add a vertical bar character after the two hyphens:
-- | Get the "block" distance of two 2D coordinate pairs manhattanDistance :: (Int, Int) -> (Int, Int) -> Int manhattanDistance (x1, y1) (x2, y2) = abs (x2 - x1) + abs (y2 - y1)
It still works even if you add a second line without the vertical bar. All comment lines until the type signature or function definition will be considered part of the Haddock comment.
-- | Get the "block" distance of two 2D coordinate pairs -- This is the sum of the absolute difference in x and y values. manhattanDistance :: (Int, Int) -> (Int, Int) -> Int manhattanDistance (x1, y1) (x2, y2) = abs (x2 - x1) + abs (y2 - y1)
You can also make a block comment in the Haddock style. It involves the same character sequences as multi line comments, but once again, you just add a vertical bar after the start sequence. The end sequence does not need the bar:
{-| Get the "block" distance of two 2D coordinate pairs This is the sum of the absolute difference in x and y values. -} manhattanDistance :: (Int, Int) -> (Int, Int) -> Int manhattanDistance (x1, y1) (x2, y2) = abs (x2 - x1) + abs (y2 - y1)
No matter which of these options you use, your comment will look the same in the final document. Next, we'll see how to generate our Haddock document. To contrast Haddock comments with normal comments, we'll add a second function in our code with a "normal" single line comment. We also need to add both functions to the export list of our module at the top:
`
haskell module Lib ( someFunc, , manhattanDistance , euclidenDistance ) where
...
-- Get the Euclidean distance of two 2D coordinate pairs (not Haddock) euclideanDistance :: (Double, Double) -> (Double, Double) -> Double euclideanDistance (x1, y1) (x2, y2) = sqrt ((x2 - x1) ^ 2 + (y2 - y1) ^ 2)
Now let's create our document!
## Creating Our Haskell Report
To generate our document, we just use the following command:
```bash
>> stack haddock
This will compile our code. At the end of the process, it will also inform us about what percentage of the elements in our code used Haddock comments. For example:
25% ( 1 / 4) in 'Lib'
Missing documentation for:
Module header
someFunc (src/Lib.hs:7)
euclideanDistance (src/Lib.hs:17)
As expected, 'euclideanDistance' is not considered to have a Haddock comment. We also haven't defined a Haddock comment for our module header. We'll do that in the next section. We'll get rid of the 'someFunc' expression, which is just a stub. This command will generate HTML files for us, most importantly an index file! They get generated in the '.stack-work' directory, usually in a folder that looks like '{project}/.stack-work/install/{os}/{hash}/{ghc_version}/doc/'. For example, the full path of my index file in this example is:
/home/HaddockTest/.stack-work/install/x86_64-linux-tinfo6/6af01190efdb20c14a771b6e2823b492cb22572e9ec30114989156919ec4ab3a/9.6.3/doc/index.html
You can open the file with your web browser, and you'll find a mostly blank page listing the modules in your project, which at this point should only be 'Lib'. If you click on 'Lib', it will take you to a page that looks like this:
We can see that all three expressions from our file are there, but only 'manhattanDistance' has its comment visible on the page. What's neat is that the type links all connect to documentation for the base libraries. If we click on 'Int', it will take us to the page for the 'base' package module 'Data.Int', giving documentation on 'Int' and other integer types.
Documenting the Module Header
In the picture above, you'll see a blank space between our module name and the 'Documentation' section. This is where the module header documentation should go. Let's see how to add this into our code. Just as Haddock comments for functions should go above their type signatures, the module comment should go above the module declaration. You can start it with the same format as you would have with other Haddock block comments:
{-| This module exposes a couple functions
related to 2D distance calculation.
-}
module Lib
( manhattanDistance
, euclideanDistance
) where
...
If you rerun 'stack haddock' and refresh your Haddock page, this comment will now appear under 'Lib' and above 'Documentation'. This is the simplest thing you can do to provide general information about the module.
Module Header Fields
However, there are also additional fields you can add to the header that Haddock will specifically highlight on the page. Suppose we update our block comment to have these lines:
{-|
Module: Lib
Description: A module for distance functions.
Copyright: (c) Monday Morning Haskell, 2023
License: MIT
Maintainer: person@mmhaskell.com
The module has two functions. One calculates the "Manhattan" distance, or "block" distance on integer 2D coordinates. The other calculates the Euclidean distance for a floating-point coordinate system.
-}
module Lib
( manhattanDistance
, euclideanDistance
) where
...
At the bottom of the multi line comment, after all the lines for the fields, we can put a longer description, as you see. After adding this, removing 'someFunc', and making our prior comment on Euclidean distance a Haddock comment, we now get 100% marks on the documentation for this module when we recompile it:
100% ( 3 / 3) in 'Lib'
And here's what our HTML page looks like now. Note how the fields we entered are populated in the small box in the upper right.
Note that the short description we gave is now visible next to the module name on the index page. This page still only contains the description below the fields.
Haddock Comments Below
So far, we've been using the vertical bar character to place Haddock comments above our type signatures. However, it is also possible to place comments below the type signatures, and this will introduce us to a new syntax technique that we'll use for other areas. The general idea is that we can use a caret character '^' instead of the vertical bar, indicating that the item we are commenting is "above" or "before" the comment. We can do this either with single line comments or block comments. Here's how we would use this technique with our existing functions:
manhattanDistance :: (Int, Int) -> (Int, Int) -> Int
-- ^ Get the "blocK" distance of two 2D coordinate pairs
manhattanDistance (x1, y1) (x2, y2) = abs (x2 - x1) + abs (y2 - y1)
euclideanDistance :: (Double, Double) -> (Double, Double) -> Double
{- ^ Get the Euclidean distance of two 2D coordinate pairs
This uses the Pythagorean formula.
-}
euclideanDistance (x1, y1) (x2, y2) = sqrt ((x2 - x1) ^ 2 + (y2 - y1) ^ 2)
The comments will appear the same in the final documentation.
Commenting Type Signatures
The comments we've written so far have described each function as a unit. However, sometimes you want to make notes on specific function arguments. The most common way to write these comments in Haskell with Haddock is with the "above" style. Each argument goes on its own line with a "caret" Haddock comment after it. Here's an example:
-- | Given a base point and a list of other points, returns
-- the shortest distance from the base point to a point in the list.
shortestDistance ::
(Double, Double) -> -- ^ The base point we are measuring from
[(Double, Double)] -> -- ^ The list of alternative points
Double
shortestDistance base [] = -1.0
shorestDistance base rest = minimum $ (map (euclideanDistance base) rest)
It is also possible to write these with the vertical bar above each argument, but then you will need a second line for the comment.
-- | Given a base point and a list of other points, returns
-- the shortest distance from the base point to a point in the list.
shortestDistance ::
-- | The base point we are measuring from
(Double, Double) ->
-- | The list of alternative points
[(Double, Double)] ->
Double
shortestDistance base [] = -1.0
shorestDistance base rest = minimum $ (map (euclideanDistance base) rest)
It is even possible to write the comments before AND on the same line as inline comments. However, this is less common since developers usually prefer seeing the type as the first thing on the line.
Commenting Constructors
You can also use Haddock comments for type definitions. Here is an example of a data type with different constructors. Each gets a comment.
data Direction =
DUp | -- ^ Positive y direction
DRight | -- ^ Positive x direction
DDown | -- ^ Negative y direction
DLeft -- ^ Negative x direction
Commenting Record Fields
You can also comment record fields within a single constructor.
data Movement = Movement
{ direction :: Direction -- ^ Which way we are moving
, distance :: Int -- ^ How far we are moving
}
An important note is that if you have a constructor on the same line as its fields, a single caret comment will refer to the constructor, not to its last field.
data Point =
Point2I Int Int | -- ^ 2d integral coordinate
Point2D Double Double | -- ^ 2d floating point coordinate
Point3I Int Int Int | -- ^ 3d integral coordinate
Point3D Double Double Double -- ^ 3d floating point coordinate
Commenting Class Definitions
As one final feature, we can add these sorts of comments to class definitions as well. With class functions, it is usually better to use "before" comments with the vertical bar. Unlike constructors and fields, an "after" comment will get associated with the argument, not the method.
{-| The Polar class describes objects which can be described
in "polar" coordinates, with a magnitude and angle
-}
class Polar a where
-- | The total length of the item
magnitude :: a -> Double
-- | The angle (in radians) of the point around the z-axis
angle :: a -> Double
Here's what all these new pieces look like in our documentation:
You can see the way that each comment is associated with a particular field or argument.
A Complete Introduction to the Haskell Programming Language
Of course, comments are useless if you have no code or projects to write them in! If you're a beginner to Haskell, the fastest way to get up to writing project-level code is our course, Haskell From Scratch! This course features hours of video lectures, over 100 programming exercises, and a final project to test your skills! Learn more about it on this page!