7.5. Syntactic extensions

7.5.1. Hierarchical Modules

GHC supports a small extension to the syntax of module names: a module name is allowed to contain a dot ‘.’. This is also known as the “hierarchical module namespace” extension, because it extends the normally flat Haskell module namespace into a more flexible hierarchy of modules.

This extension has very little impact on the language itself; modules names are always fully qualified, so you can just think of the fully qualified module name as "the module name". In particular, this means that the full module name must be given after the module keyword at the beginning of the module; for example, the module A.B.C must begin

module A.B.C

It is a common strategy to use the as keyword to save some typing when using qualified names with hierarchical modules. For example:

import qualified Control.Monad.ST.Strict as ST

Hierarchical modules have an impact on the way that GHC searches for files. For a description, see Section 4.9.3.

GHC comes with a large collection of libraries arranged hierarchically; see the accompanying library documentation. There is an ongoing project to create and maintain a stable set of "core" libraries used by several Haskell compilers, and the libraries that GHC comes with represent the current status of that project. For more details, see Haskell Libraries.

7.5.2. Pattern guards

The discussion that follows is an abbreviated version of Simon Peyton Jones's original proposal. (Note that the proposal was written before pattern guards were implemented, so refers to them as unimplemented.)

Suppose we have an abstract data type of finite maps, with a lookup operation:
lookup :: FiniteMap -> Int -> Maybe Int
The lookup returns Nothing if the supplied key is not in the domain of the mapping, and (Just v) otherwise, where v is the value that the key maps to. Now consider the following definition:

clunky env var1 var2 | ok1 && ok2 = val1 + val2
| otherwise  = var1 + var2
where
  m1 = lookup env var1
  m2 = lookup env var2
  ok1 = maybeToBool m1
  ok2 = maybeToBool m2
  val1 = expectJust m1
  val2 = expectJust m2

The auxiliary functions are

maybeToBool :: Maybe a -> Bool
maybeToBool (Just x) = True
maybeToBool Nothing  = False

expectJust :: Maybe a -> a
expectJust (Just x) = x
expectJust Nothing  = error "Unexpected Nothing"

What is clunky doing? The guard ok1 && ok2 checks that both lookups succeed, using maybeToBool to convert the Maybe types to booleans. The (lazily evaluated) expectJust calls extract the values from the results of the lookups, and binds the returned values to val1 and val2 respectively. If either lookup fails, then clunky takes the otherwise case and returns the sum of its arguments.

This is certainly legal Haskell, but it is a tremendously verbose and un-obvious way to achieve the desired effect. Arguably, a more direct way to write clunky would be to use case expressions:

clunky env var1 var1 = case lookup env var1 of
  Nothing -> fail
  Just val1 -> case lookup env var2 of
    Nothing -> fail
    Just val2 -> val1 + val2
where
  fail = val1 + val2

This is a bit shorter, but hardly better. Of course, we can rewrite any set of pattern-matching, guarded equations as case expressions; that is precisely what the compiler does when compiling equations! The reason that Haskell provides guarded equations is because they allow us to write down the cases we want to consider, one at a time, independently of each other. This structure is hidden in the case version. Two of the right-hand sides are really the same (fail), and the whole expression tends to become more and more indented.

Here is how I would write clunky:

clunky env var1 var1
  | Just val1 <- lookup env var1
  , Just val2 <- lookup env var2
  = val1 + val2
...other equations for clunky...

The semantics should be clear enough. The qualifers are matched in order. For a <- qualifier, which I call a pattern guard, the right hand side is evaluated and matched against the pattern on the left. If the match fails then the whole guard fails and the next equation is tried. If it succeeds, then the appropriate binding takes place, and the next qualifier is matched, in the augmented environment. Unlike list comprehensions, however, the type of the expression to the right of the <- is the same as the type of the pattern to its left. The bindings introduced by pattern guards scope over all the remaining guard qualifiers, and over the right hand side of the equation.

Just as with list comprehensions, boolean expressions can be freely mixed with among the pattern guards. For example:

f x | [y] <- x
    , y > 3
    , Just z <- h y
    = ...

Haskell's current guards therefore emerge as a special case, in which the qualifier list has just one element, a boolean expression.

7.5.3. Parallel List Comprehensions

Parallel list comprehensions are a natural extension to list comprehensions. List comprehensions can be thought of as a nice syntax for writing maps and filters. Parallel comprehensions extend this to include the zipWith family.

A parallel list comprehension has multiple independent branches of qualifier lists, each separated by a `|' symbol. For example, the following zips together two lists:

   [ (x, y) | x <- xs | y <- ys ] 

The behavior of parallel list comprehensions follows that of zip, in that the resulting list will have the same length as the shortest branch.

We can define parallel list comprehensions by translation to regular comprehensions. Here's the basic idea:

Given a parallel comprehension of the form:

   [ e | p1 <- e11, p2 <- e12, ... 
       | q1 <- e21, q2 <- e22, ... 
       ... 
   ] 

This will be translated to:

   [ e | ((p1,p2), (q1,q2), ...) <- zipN [(p1,p2) | p1 <- e11, p2 <- e12, ...] 
                                         [(q1,q2) | q1 <- e21, q2 <- e22, ...] 
                                         ... 
   ] 

where `zipN' is the appropriate zip for the given number of branches.

7.5.4. Rebindable syntax

GHC allows most kinds of built-in syntax to be rebound by the user, to facilitate replacing the Prelude with a home-grown version, for example.

You may want to define your own numeric class hierarchy. It completely defeats that purpose if the literal "1" means "Prelude.fromInteger 1", which is what the Haskell Report specifies. So the -fno-implicit-prelude flag causes the following pieces of built-in syntax to refer to whatever is in scope, not the Prelude versions:

Be warned: this is an experimental facility, with fewer checks than usual. In particular, it is essential that the functions GHC finds in scope must have the appropriate types, namely:
	        fromInteger  :: forall a. (...) => Integer  -> a
		fromRational :: forall a. (...) => Rational -> a
		negate       :: forall a. (...) => a -> a
		(-)          :: forall a. (...) => a -> a -> a
		(>>=)	     :: forall m a. (...) => m a -> (a -> m b) -> m b
		(>>)	     :: forall m a. (...) => m a -> m b -> m b
		return	     :: forall m a. (...) => a      -> m a
		fail	     :: forall m a. (...) => String -> m a
	     
(The (...) part can be any context including the empty context; that part is up to you.) If the functions don't have the right type, very peculiar things may happen. Use -dcore-lint to typecheck the desugared program. If Core Lint is happy you should be all right.