stores information about the screen dimension as well as any gaps which should be left at the edges of the screen for status bars and other such things. Now, you may wonder why we have

newtype ScreenId = S Intderiving(...)

rather than just

type ScreenId =Int

? The reason is that if

type ScreenId =Int

, it would be possible to accidentally do arithmetic with

ScreenId

s mixed with other

Int

values, which clearly doesn't make sense. Making

ScreenId

a separate type means that the type system will enforce non-mixing of

ScreenId

s with other types, while using

newtype

with automatic instance deriving means none of the convenience is lost -- we can write code just as if

ScreenId

s are normal

Int

values, but be sure that we can't accidentally get mixed up and do something silly like add a

ScreenId

to the width of a window. This is the power of a rich static type system like Haskell's -- we can encode certain invariants and constraints in the type system, and have them automatically checked at compile time.

An

XConf

record stores xmonad's (immutable) configuration data, such as window border colors, the keymap, information about the X11 display and root window, and other user-specified configuration information. The reason this record is separated from

XState

is that, as we'll see later, xmonad's code provides a static guarantee that the data stored in this record is truly read-only, and cannot be changed while xmonad is running.

XConfig

provides a way for the user to customize xmonad's configuration, by defining an

XConfig

record in their

xmonad.hs

file. You're probably already familiar with this record type.

2 The

X

monad

The X monad represents a common pattern for building custom monad instances: using monad transformers, one can simply 'layer' the capabilities and effects of several monads into one, and then use GHC's newtype deriving capabilities to automatically derive instances of the relevant type classes. In this case, the base monad out of which the X monad is built is IO; this is necessary since communicating with the X server involves IO operations. On top of that is

Along with the X monad, several utility functions are provided, such as

runX

, which turns an action in the X monad into an IO action, and

catchX

, which provides error handling for X actions. There are also several higher-order functions provided for convenience, including

withDisplay

(apply a function producing an X action to the current display) and

withWindowSet

(apply a function to the current window set).

3

ManageHook

This is a bit more advanced, and not really a central part of the system, so I'm skipping it for now, hopefully coming back to add more later.

4

LayoutClass

Next, let's take a look at the

LayoutClass

. This is one of the places that Haskell's type classes really shine.

LayoutClass

is a type class of which every layout must be an instance. It defines the basic functions which define what a layout is and how it behaves. The comments in the source code explain very clearly what each of these functions is supposed to do, but here are some highlights:

Note that all the

LayoutClass

functions provide default implementations, so that

LayoutClass

instances do not have to provide implementations of those functions where the default behavior is desired. For example, by default,

doLayout

simply calls

pureLayout

, so a layout that does not require access to the X monad need only implement the

pureLayout

function. (For example, the Accordion, Square, and Grid layouts from the contrib library all use this approach.)

Layouts can have their own private state, by storing this state in the

LayoutClass

instance and returning a modified structue (via

doLayout

) when the state changes.

Both

doLayout

and

handleMessage

have corresponding "pure" versions, which do not give results in the X monad. These functions are never called directly by the xmonad core, which only calls

doLayout

and

handleMessage

, but a layout may choose to implement one (or both) of these "pure" functions, which will be called by the default implementation of the "impure" versions. Layouts which implement

pureLayout

or

pureMessage

are guaranteed to only make decisions about layout or messages (respectively) based on the internal layout state, and not on the state of the system or the window manager in general.

Now, every distinct layout will have a distinct type, although of course they all must be instances of

LayoutClass

. Given Haskell's strong typing, how can we store different layouts with different workspaces, or even change layouts on the fly? The solution is to wrap layouts using an existential type which hides the particular layout type and only exposes the fact that it is an instance of

LayoutClass

. Not only does this solve the problems caused by separate types for each layout, but it also guarantees that the xmonad core can only ever interact with a layout by calling functions from its

LayoutClass

instance. (Actually, this is a lie, since

doLayout

and

handleMessage

give results in the

X

monad, meaning they have access to the window manager state...)

-- | An existential type that can hold any object that is in Read and LayoutClass.data Layout a =forall l.(LayoutClass l a,Read(l a))=> Layout (l a)

The

forall l.

abstracts over all layout types (types which are an instance of

LayoutClass

); this is what makes it an existential type. Note that

l

does not appear on the left-hand side of the type declaration. A variable

lay

may have the highly specific type of some particular layout (for example, the types of layouts formed by stacking various layout combinators and transformers can get rather long and hairy!), but no matter what the type of

lay

,

Layout lay

will simply have the type

Layout a

for some window type

a

(usually

Window

).
Note that

(l a)

is also required to be an instance of the

Read

type class -- this is so the state of all the layouts can be serialized and then read back in during dynamic restarts.

5 Messages

The xmonad core uses messages to communicate with layouts. The obvious, simple way to define messages would be by defining a new data type, something like

thus defining three messages types, which tell a layout to hide itself, release any resources, and display a monkey, respectively.

The problem with such an approach should be obvious: it is completely inflexible and inextensible; adding new message types later would be a pain, and it would be practically impossible for extension layouts to define their own message types without modifying the core. So, xmonad uses a more sophisticated system (at the cost of making things slightly harder to read and understand).