Small changes to XMonad like changing keyboard shortcuts require little Haskell knowledge. However, bigger customizations, especially to organize your code when adapting other people’s configurations, are easier when you understand Haskell functions.

Function notation

Briefly, a function maps a thing to another thing. It takes in a thing as input and produces another thing as output.

You surely know the following function notation from math class in high school:

f(x) = x + 1

Here, f is the function name, x is a variable, a placeholder that you can plug values into, and x + 1 tells you how to compute the result. Your teacher may have told you that you were allowed to plug in a real number for x into f and got a real number as a result.

However, mathematicians normally leave no doubt about what is allowed to go in and what can come out. They use a slightly improved notation that you may not have used in school. It includes “the type of this function”.

Math:
f : ℤ -> ℤ   -- math "type signature"/declaration
f(x) = x + 1 -- function body/definition

In math you write a “type signature” for a function using a single colon :. The whole line

f : ℤ -> ℤ   -- math "type signature"/declaration

is the signature of f, which tells you that the type of f is:

ℤ -> ℤ

Briefly, a type is the name for a collection of values. For example, ℤ is the name for the collection of integer numbers, and ℤ -> ℤ is the name for the collection of all possible functions that accept an integer as input and return an integer as output. From this notation it is obvious that our function f only allows us plug in an integer and computes an integer as a result.

Once we have defined a function f, we can use/apply it as follows:

f(3)         -- function usage/application, result=4

You write the name f followed by your value 3 inside parentheses (...) and the expression f(3) represents the resulting number 4.

Haskell is hugely inspired by math, so it’s no wonder that its notation for functions is quite similar to math. You would write the same function f in Haskell as follows:

f :: Int -> Int -- haskell type signature/declaration
f x = x + 1     -- function body/definition

f 3             -- function usage/application, result=4

The minor differences between Haskell and math are: You use a double colon :: instead of a single colon in the type signature, you write Int for the integer numbers and you leave out the parentheses (...) when you define or use a function. Instead you just separate the function name f from the variable x or a value 3 with a single space like f x. Again the expression f 3 represents the resulting number 4.

In Haskell the type signature notation using :: is also extended and used for constants. For example, in XMonad you write

myTerminalApp :: String
myTerminalApp = "gnome-terminal"

myWorkspaces :: [String]
myWorkspaces = ["1","2","3"]

for the terminal setting and workspace setting. The type signatures tell you precisely that myTerminalApp is a string and myWorkspaces a list of strings.

However, one huge benefit of Haskell is that you often don’t need to write type signatures. Type signatures always exist, but Haskell can generate them automatically in many situations. In many XMonad example configurations you won’t find many (if any) type signatures:

myTerminalApp = "gnome-terminal"

myWorkspaces = ["1","2","3"]

It is very obvious that myTerminalApp is a string and myWorkspaces is a list of strings, so Haskell will generate the type signature automatically. You can leave a type signature out, when the type is obvious to you (and Haskell).

Another place in XMonad where you wouldn’t write a type signature is the keymaps setting:

import qualified Data.Map as M

-- myKeysFunc :: XConfig Layout -> M.Map (ButtonMask,KeySym) (X ())
myKeysFunc baseConfig = M.fromList \$
[ ((mod4Mask, xK_Return), spawn \$ terminal baseConfig)
]
-- myConfig :: XConfig (ModifiedLayout.... )
myConfig = def
{ keys = myKeysFun -- override previous keymaps
}

The type signatures of myKeysFunc and myConfig are very long, difficult to write and difficult to read. It is not useful to write them yourself, because Haskell will generate these signatures automatically. For example, Haskell knows that def is a value of type XConfig and that you only modify a single setting of it with the { .. } record notation. It will therefore conclude and know that myConfig must also the a value of type XConfig.

Benefits of writing type signatures in Haskell

Just as the type Int is a collection of integer numbers and type Char is a collection of single letters, digits (and other symbols) so is Int -> Int a collection of all possible functions from Int to Int (You can read the arrow -> as “to”). And just as 'a' is a value of this collection Char and therefore has type Char, this specific function f is a value of the collection Int -> Int and therefore has type Int -> Int ( we say f has type “Int to Int).

A type signature of a function serves two purposes: First, it tells you a lot about what a function does without looking into or caring about the function body/definition, thus, without knowing how it does it.

toUpper :: Char -> Char

For example, the signature for the built-in Haskell function toUpper tells you that it takes in a value of type Char and produces another value of type Char. Together with its name you can guess what it does: It turns a lowercase letter like 'a' into an uppercase letter like 'A'. But you don’t know or care how it does it.

Second, a type signature allows the Haskell type checker to cross-check your code. For example in the following code

myTerminalApp :: String
myTerminalApp = 3 -- error, no type match
the type checker will conclude (using the function body alone) that myTerminalApp must be an integer. The checker will compare this computed type to the type you have written in your type signature. And because these don’t match, the checker will give you an error.
Importing Modules with import

Haskell programmers separate all their code into “modules”, Roughly, a module is a single Haskell file. With import statements you can make functions of other modules available and usable in your current file/module. For example, you can make the XMonad functions and values like xmonad and def available in your configuration file with

There are several styles to import a module. We will use the noBorders and smartBorders functions from XMonad.Layout.NoBorders module (to remove borders around your windows), and the fromList function from the Data.Map module (to create keyboard shortcuts for the keys setting) as our examples.

The most basic style is to import modules as a whole:

import Data.Map

You write these lines at the top of your configuration file to import all functions in that module. However, as your configuration file gets bigger, the number of your imported modules will grow large. So when you see fromList in your code some months later, you may have forgotten which module this function came from. To avoid this you should use one of the following two styles of import:

First, you can list the imported functions and values explicitly.

import Data.Map (fromList)

Here you will only make the fromList, noBorders and smartBorders functions available. Only use the explicit import if you only need one or up to four functions/values, otherwise this list will get too long.

Second, you can use a “qualified import” to make all functions from a module available but with a prefix that tells you which module a functions comes from:

import qualified Data.Map as M

Here in your code you will have to write M.fromList with the M. prefix (as in keys setting type example) and N.noBorders and N.smartBorders with the N. prefix. With these prefixes you will always know which module these functions come from. Use this style of import when you need to import five or more functions from a less frequently used or known module. Don’t use this style of import for well-known or popular modules like XMonad; you always import well-known modules normally:

You and many other people who may read your configuration will know with time which functions come come from the XMonad module.

Actually, there are even more styles. If you are interested, you can read about all the styles to import modules.

Dollar operator \$

Roughly, the dollar operator separates code on its left from code on its right. This function is used to reduce the number of parentheses to make your configuration easier to read. For example, you may have the following function:

addOne x = x + 1     -- function body/definition

You can use and apply it as follows:

This expression is valid, because first you compute addOne 1 and the result 2 is given to the other addOne. Here you have to write parentheses, because leaving them out as in

will be interpreted by Haskell as the invalid expression:

This expression will result in an error, because addOne only accepts integer values like 1 but not a function value like addOne. So you can either use parentheses or you can use the \$ operator as follows:

It will separate its left side from its right side and be interpreted as:

The benefit of using \$ in this example is small, but it is huge when your code is already inside many parentheses. For example, for the XMonad keys setting you often see a lot of parentheses:

myKeysFunc baseConfig = M.fromList (
[ ((mod4Mask, xK_Return),          spawn (terminal baseConfig))
])

Here the spawn function accepts the name an app as a string value, which is extracted first from baseConfig with the terminal accessor function using parentheses.
[Accessor functions.]
Similarly, the windows function accepts a single window operation value, which is constructed using shift "1" inside parentheses. Also the M.fromList function accepts a single list, which is wrapped in parentheses. You can make this code a lot easier to read by using \$ as follows:

myKeysFunc baseConfig = M.fromList \$
[ ((mod4Mask, xK_Return),          spawn \$ terminal baseConfig)
]

Again, the dollar function separates code on its left from code on its right but only inside parentheses. For example, this is why the \$ operator to the right of the spawn function only separates (spawn) (terminal baseConfig), but does not separate all the code from the left and above it from all the code on the right and below it.

Dot operator .

It “composes” two functions and is written in the form:

<second function> . <first function>

When we say “compose” we mean that it combines two functions to create a new function just like the + operator combines two numbers to create a new number. This new function applies the first function, takes its result and passes it to the second function. You can compose any two functions where the output of the first one can be plugged into the second one.

In the following contrived example we have two functions:

addOne x = x + 1

square x = x * x

We compose them with the dot operator and apply the resulting function to 2 as follows:

composedFunc 2

These two functions are composable because the output of addOne is a number that we can plug into square. The resulting functions (square . addOne) and composedFunc (both are the same) are applied to 2 and compute:

That is, they first add one to 2, and then square the result 3 to 9.

If you compose more than two functions, the dot operator composes from right to left. So

(third . second . first) 2

computes

third (second (first 2) )

In XMonad this dot operator becomes handy to set the keys setting as follows:

import qualified Data.Map as M

-- myKeys :: XConfig ... -> M.Map ...
myKeys baseConfig@(XConfig {modMask = modKey}) =
[ ((modKey, xK_f), spawn "firefox"))
, ((modKey .|. shiftMask, xK_1), windows (shift "1"))
-- ...
]

myConfig = def
{ keys = M.fromList . myKeys-- override previous keymaps
}

This M.fromList . myKeys thing is a new function that expects an XConfig value and constructs a Haskell “Map” of keymaps: precisely what keys has to be. This works because we first apply myKeys, which expects an XConfig value and computes a list of keymaps, and then pass the resulting list to M.fromList, which as its name suggests expects a list of keymaps and produces a “Map” of keymaps.

Lists

Lists are used throughout XMonad and are easy to recognize and to write. You create lists in Haskell with square brackets.

list1 = ["1","2","3"]

You can also write each element on a new line:

list2 =
[ 1
, 2
, 3
, 4
, 5
]

Just make sure that the lines are indented. This style is easier to read when the elements are long to write.

All the elements in that list must be of the same type. For some types like integer numbers there is a range notation with ... For example, you can use this notation on integers and XMonad keyboard key representation numbers.

list3 = [1..5] -- result=[1,2,3,4,5]
list4 = [xK_1..xK_5] -- result=[xK_1,xK_2,xK_3,xK_4,xK_5]

You can combine two lists with the ++ operator:

list5 = [1,2] ++ [3,4] -- result=[1,2,3,4]

This ++ operator is often used in the keymaps section of your XMonad configuration file to give your (possibly long) list of keymaps a little bit more structure.

myKeys =
-- Change XMonad built-in key combinations
[ ("M-S-1", windows (shift "1:work") )
-- ...
]
++
[ ("M-f", spawn "firefox")
-- ...
]

A notation called list comprehension can be used to construct a new list out of combinations of elements of existing lists. The notation in Haskell is as follows:

[ <do something with values> | <value1> <- <somelist1>, <value2> <- <somelist2>]

A concrete example is

myPairs = [ (number, name) | number <- [1,2], name <- ["John", "Alice"]

which constructs a list called myPairs that contains all combinations of those numbers and names:

[(1,"John"), (2,"John"), (1,"Alice"), (2,"Alice")]

You can also go through a list of pairs like

[ number | (number, name) <- myPairs]

for example to collect only the numbers of myPairs:

[1,2,1,2]

In XMonad “list comprehension” is used and needed when you change the names of workspaces, because you will have to modify a lot of workspace keymaps. You will often see something like this:

[ ("M-" ++ otherModKey ++ [key], windows \$ action workspaceID)
| (key, workspaceID)  <-   [("1","1:work"), ("2","2:browser")]
, (otherModKey, action) <- [("", view), ("S-", shift)]
]

See if you can understand why this is the same as:

[ ("M-1", windows \$ view "1:work")
, ("M-2", windows \$ view "2:browser")
, ("M-S-1", windows \$ shift "1:work")
, ("M-S-2", windows \$ shift "2:browser")
]
zip function

You often see the zip function in XMonad when you modify the keymaps for your workspaces. It combines two lists of single elements into a single list of paired elements. For example

zip [1,2,3] ["one","two","three"]

computes the list of pairs:

[(1,"one"), (2,"two"), (3,"three")]

The first element of the first list is paired with the first element of the second list, and so on.

In XMonad you will often see code like

zip (workspaces baseConfig) [xK_1 .. xK_9]

which is the same as:

workspaceNames = ["1","2","3"]
xkeys = [xK_1, x_K_2, xK_3]

zip workspaceNames xkeys

and computes the following list:

[("1",xK_1), ("2",xK_2), ("3",xK_3)]

Take a look at its documention page.

Infix notation `...`

If you surround a function that takes precisely two arguments with backticks `...`, you can use it “infix”. That is, you can use it inbetween two values.

For example, a function like

plus :: Int -> Int -> Int
plus x y = x+y

is normally used in Haskell as follows:

plus 1 2 -- result=3

If you write plus using the infix notation you can use the plus function more readably as follows:

1 `plus` 2 -- result=3

In XMonad configurations you will often encounter this notation with the additionalKeysP function when setting up keymaps with the EZ notation.

myKeys =
[ ("M-S-1", windows (shift "1") )
, ("M-f", spawn "firefox")
-- ...
]
myConfig = def
{ -- other settings

Here, additionalKeysP is written infix with backticks rather than following, regular but harder to read style:

{ -- other settings
}) myKeys

Malte Neuss

Java Software Engineer by day, Haskell enthusiast by night.

Other Posts In Series

Small changes to XMonad like changing keyboard shortcuts require little Haskell knowledge. However, bigger customizations, especially adapting other people’s configurations, are easier when you understand Haskell types and records.

Configure a light-weight status bar for XMonad: Xmobar

The status bar Xmobar works out of the box on the top edge of your screen and shows useful system info. However, if you want to make it look nice and change what information to show, you can customize it with a tiny bit of Haskell.