diff --git a/ginger.cabal b/ginger.cabal
--- a/ginger.cabal
+++ b/ginger.cabal
@@ -1,8 +1,8 @@
--- Initial ginger.cabal generated by cabal init.  For further 
+-- Initial ginger.cabal generated by cabal init.  For further
 -- documentation, see http://haskell.org/cabal/users-guide/
 
 name:                ginger
-version:             0.1.1.1
+version:             0.1.1.2
 synopsis:            An implementation of the Jinja2 template language in Haskell
 description:         Ginger is Jinja, minus the most blatant pythonisms. Wants
                      to be feature complete, but isn't quite there yet.
@@ -11,10 +11,10 @@
 license-file:        LICENSE
 author:              Tobias Dammers
 maintainer:          tdammers@gmail.com
--- copyright:           
+-- copyright:
 category:            Text
 build-type:          Simple
--- extra-source-files:  
+-- extra-source-files:
 cabal-version:       >=1.10
 
 library
@@ -24,8 +24,8 @@
                  , Text.Ginger.Html
                  , Text.Ginger.Parse
                  , Text.Ginger.Run
-  -- other-modules:       
-  -- other-extensions:    
+  -- other-modules:
+  -- other-extensions:
   build-depends: base >=4.5 && <5
                , aeson
                , bytestring
diff --git a/src/Text/Ginger.hs b/src/Text/Ginger.hs
--- a/src/Text/Ginger.hs
+++ b/src/Text/Ginger.hs
@@ -31,13 +31,21 @@
 --   >     </body>
 --   > </html>
 
--- There are two kinds of delimiters. `{% ... %}` and `{{ ... }}`. The first
+-- | There are two kinds of delimiters: @{% ... %}@ and @{{ ... }}@. The first
 -- one is used to execute statements such as for-loops or assign values, the
 -- latter prints the result of an expression to the template.
+--
+-- Both kinds of delimiters support adding a dash (@-@) on either side or both,
+-- putting them in whitespace-eating mode on the respective side.
+-- Whitespace-eating means that any whitespace that precedes cq. follows the
+-- delimited construct is removed, including newlines. For example,
+-- @A {{ "b" }}@ renders as @\"A b\"@, but @A {{- "b" }}@ renders as @\"Ab\"@.
+-- Note that whitespace /inside/ delimiters is never printed; the dash only
+-- ever removes whitespace on the outside.
 
--- /Not implemented yet/: Jinja2 allows the programmer to override the default
--- tags from @{% %}@ and @{{ }}@ to different tokens, e.g. @\<% %\>@ and @\<\<
--- \>\>@.  Ginger does not currently support this.
+-- | /Not implemented yet/: Jinja2 allows the programmer to override the
+-- default tags from @{% %}@ and @{{ }}@ to different tokens, e.g. @\<% %\>@
+-- and @\<\< \>\>@.  Ginger does not currently support this.
 
 -- ** Variables
 -- | You can mess around with the variables in templates provided they are
@@ -51,12 +59,12 @@
 
 -- | @
 -- {{ foo.bar }}
--- {{ foo['bar'] }}
+-- {{ foo[\'bar\'] }}
 -- @
 
 -- | It’s important to know that the curly braces are /not/ part of the
 -- variable, but the print statement. If you access variables inside tags
--- don’t put the braces around them.
+-- don't put the braces around them.
 
 -- | If a variable or attribute does not exist you will get back an undefined
 -- value. What you can do with that kind of value depends on the
@@ -172,10 +180,16 @@
 
 -- | > {{ append(x, "foobar") }}
 
+-- | A list of available filters can be found in the 'Text.Ginger.Run' module.
+
 -- | /Deviation from Jinja2:/ Ginger does not distinguish between filters and
 -- functions at the semantics level; any function can be called as a filter,
 -- and vv., and the filter syntax is merely a syntactic variant of a function
--- call.
+-- call. Ginger doesn't really distinguish between functions and values,
+-- either: a function is a value that happens to be callable; other than that,
+-- functions live in the same namespace as any other value, and if you bind
+-- a function to a context value on the host side, you can use it just like
+-- any other function / filter.
 
 -- * Haskell API
 -- ** General
@@ -212,6 +226,7 @@
 -- have pre-loaded all template sources), you can use the pure 'parseGinger'
 -- flavor, which does not rely on a host monad.
   module Text.Ginger.Parse
+
 -- ** Running
 -- | The core function for running a template is 'runGinger' (or its monadic
 -- flavor 'runGingerT'); in order to pass an initial context to the template
@@ -221,11 +236,15 @@
 -- | An example call (for running a template in 'IO') would look something like
 -- this:
 
--- > runGingerT (makeContextM scopeLookup (putStr . Text.unpack . htmlSource)) tpl
+-- | > runGingerT (makeContextM scopeLookup (putStr . Text.unpack . htmlSource)) tpl
+
 , module Text.Ginger.Run
+
 -- ** Other concerns
--- | Ginger's unitype value
+-- *** GVal: Ginger's unitype value
 , module Text.Ginger.GVal
+
+-- *** AST
 -- | The data structures used to represent templates, statements and
 -- expressions internally.
 , module Text.Ginger.AST
diff --git a/src/Text/Ginger/AST.hs b/src/Text/Ginger/AST.hs
--- a/src/Text/Ginger/AST.hs
+++ b/src/Text/Ginger/AST.hs
@@ -22,12 +22,12 @@
         }
         deriving (Show)
 
--- | A macro definition ( {% macro %}
+-- | A macro definition ( @{% macro %}@ )
 data Macro
     = Macro { macroArgs :: [VarName], macroBody :: Statement }
     deriving (Show)
 
--- | A block definition ( {% block %}
+-- | A block definition ( @{% block %}@ )
 data Block
     = Block { blockBody :: Statement } -- TODO: scoped blocks
     deriving (Show)
diff --git a/src/Text/Ginger/GVal.hs b/src/Text/Ginger/GVal.hs
--- a/src/Text/Ginger/GVal.hs
+++ b/src/Text/Ginger/GVal.hs
@@ -6,7 +6,12 @@
 -- Ginger can understand.
 --
 -- Most of the types in this module are parametrized over an 'm' type, which
--- is the host monad for template execution, as passed to 'runGingerT'.
+-- is the host monad for template execution, as passed to 'runGingerT'. For
+-- most kinds of values, 'm' is transparent, and in many cases a 'ToGVal'
+-- instance can be written that works for all possible 'm'; the reason we need
+-- to parametrize the values themselves over the carrier monad is because we
+-- want to support impure functions, which requires access to the underlying
+-- carrier monad (e.g. 'IO').
 module Text.Ginger.GVal
 where
 
@@ -53,53 +58,34 @@
 
 import Text.Ginger.Html
 
--- | A function that can be called from within a template execution context.
-type Function m = [(Maybe Text, GVal m)] -> m (GVal m)
-
--- | Match arguments passed to a function at runtime against a list of declared
--- argument names.
--- @matchFuncArgs argNames argsPassed@ returns @(matchedArgs, positionalArgs, namedArgs)@,
--- where @matchedArgs@ is a list of arguments matched against declared names
--- (by name or by position), @positionalArgs@ are the unused positional
--- (unnamed) arguments, and @namedArgs@ are the unused named arguments.
-matchFuncArgs :: [Text] -> [(Maybe Text, GVal m)] -> (HashMap Text (GVal m), [GVal m], HashMap Text (GVal m))
-matchFuncArgs names args =
-    (matched, positional, named)
-    where
-        positionalRaw = [ v | (Nothing, v) <- args ]
-        namedRaw = HashMap.fromList [ (n, v) | (Just n, v) <- args ]
-        fromPositional = Prelude.zip names positionalRaw
-        numPositional = Prelude.length fromPositional
-        namesRemaining = Prelude.drop numPositional names
-        positional = Prelude.drop numPositional positionalRaw
-        fromNamed = catMaybes $ (List.map lookupName namesRemaining)
-        lookupName n = do
-            v <- HashMap.lookup n namedRaw
-            return (n, v)
-        matched = HashMap.fromList $ fromPositional ++ fromNamed
-        named = HashMap.difference namedRaw (HashMap.fromList fromNamed)
-
--- | Ginger value.
+-- * The Ginger Value type
+--
+-- | A variant type designed as the unitype for the template language. Any
+-- value referenced in a template, returned from within a template, or used
+-- in a template context, will be a 'GVal'.
+-- @m@, in most cases, should be a 'Monad'.
+--
+-- | Some laws apply here, most notably:
+-- - when 'isNull' is 'True', then all of 'asFunction', 'asText', 'asNumber',
+--   'asHtml', 'asList', 'asDictItems', and 'length' should produce 'Nothing'
+-- - when 'isNull' is 'True', then 'asBoolean' should produce 'False'
+-- - when 'asNumber' is not 'Nothing', then 'asBoolean' should only return
+--   'False' for exactly zero
+-- - 'Nothing'-ness of 'length' should match one or both of 'asList' / 'asDictItems'
 data GVal m =
     GVal
-        { asList :: Maybe [GVal m]
-        , asDictItems :: Maybe [(Text, GVal m)]
-        , asLookup :: Maybe (Text -> Maybe (GVal m))
-        , asHtml :: Html
-        , asText :: Text
-        , asBoolean :: Bool
-        , asNumber :: Maybe Scientific
-        , asFunction :: Maybe (Function m)
-        , length :: Maybe Int
-        , isNull :: Bool
+        { asList :: Maybe [GVal m] -- ^ Convert value to list, if possible
+        , asDictItems :: Maybe [(Text, GVal m)] -- ^ Convert value to association list ("dictionary"), if possible
+        , asLookup :: Maybe (Text -> Maybe (GVal m)) -- ^ Convert value to a lookup function
+        , asHtml :: Html -- ^ Render value as HTML
+        , asText :: Text -- ^ Render value as plain-text
+        , asBoolean :: Bool -- ^ Get value's truthiness
+        , asNumber :: Maybe Scientific -- ^ Convert value to a number, if possible
+        , asFunction :: Maybe (Function m) -- ^ Access value as a callable function, if it is one
+        , length :: Maybe Int -- ^ Get length of value, if it is a collection (list/dict)
+        , isNull :: Bool -- ^ Check if the value is null
         }
 
-isList :: GVal m -> Bool
-isList = isJust . asList
-
-isDict :: GVal m -> Bool
-isDict = isJust . asDictItems
-
 -- | The default 'GVal' is equivalent to NULL.
 instance Default (GVal m) where
     def = GVal
@@ -115,14 +101,6 @@
             , length = Nothing
             }
 
--- | Types that implement conversion to 'GVal'
-class ToGVal m a where
-    toGVal :: a -> GVal m
-
--- | Trivial instance for 'GVal' itself
-instance ToGVal m (GVal m) where
-    toGVal = id
-
 -- | For convenience, 'Show' is implemented in a way that looks similar to
 -- JavaScript / JSON
 instance Show (GVal m) where
@@ -143,77 +121,51 @@
 instance ToHtml (GVal m) where
     toHtml = asHtml
 
--- | Treat a 'GVal' as a flat list and look up a value by integer index.
--- If the value is not a List, or if the index exceeds the list length,
--- return 'Nothing'.
-lookupIndex :: Int -> GVal m -> Maybe (GVal m)
-lookupIndex = lookupIndexMay . Just
 
--- | Helper function; look up a value by an integer index when the index may or
--- may not be available. If no index is given, return 'Nothing'.
-lookupIndexMay :: Maybe Int -> GVal m -> Maybe (GVal m)
-lookupIndexMay i v = do
-    index <- i
-    items <- asList v
-    atMay items index
-
-lookupKey :: Text -> GVal m -> Maybe (GVal m)
-lookupKey k v = do
-    lf <- asLookup v
-    lf k
-
--- | Loosely-typed lookup: try dictionary-style lookup first (treat index as
--- a string, and container as a dictionary), if that doesn't yield anything
--- (either because the index is not string-ish, or because the container
--- doesn't provide dictionary-style access), try index-based lookup.
-lookupLoose :: GVal m -> GVal m -> Maybe (GVal m)
-lookupLoose k v =
-    lookupKey (asText k) v <|> lookupIndexMay (floor <$> asNumber k) v
-
--- | Treat a 'GVal' as a dictionary and list all the keys, with no particular
--- ordering.
-keys :: GVal m -> Maybe [Text]
-keys v = Prelude.map fst <$> asDictItems v
-
--- | Convert a 'GVal' to a number.
-toNumber :: GVal m -> Maybe Scientific
-toNumber = asNumber
+-- * Representing functions as 'GVal's
+--
+-- | A function that can be called from within a template execution context.
+type Function m = [(Maybe Text, GVal m)] -> m (GVal m)
 
--- | Convert a 'GVal' to an 'Int'.
--- The conversion will fail when the value is not numeric, and also if
--- it is too large to fit in an 'Int'.
-toInt :: GVal m -> Maybe Int
-toInt x = toNumber x >>= toBoundedInteger
+-- | Match arguments passed to a function at runtime against a list of declared
+-- argument names.
+-- @matchFuncArgs argNames argsPassed@ returns @(matchedArgs, positionalArgs, namedArgs)@,
+-- where @matchedArgs@ is a list of arguments matched against declared names
+-- (by name or by position), @positionalArgs@ are the unused positional
+-- (unnamed) arguments, and @namedArgs@ are the unused named arguments.
+matchFuncArgs :: [Text] -> [(Maybe Text, GVal m)] -> (HashMap Text (GVal m), [GVal m], HashMap Text (GVal m))
+matchFuncArgs names args =
+    (matched, positional, named)
+    where
+        positionalRaw = [ v | (Nothing, v) <- args ]
+        namedRaw = HashMap.fromList [ (n, v) | (Just n, v) <- args ]
+        fromPositional = Prelude.zip names positionalRaw
+        numPositional = Prelude.length fromPositional
+        namesRemaining = Prelude.drop numPositional names
+        positional = Prelude.drop numPositional positionalRaw
+        fromNamed = catMaybes $ (List.map lookupName namesRemaining)
+        lookupName n = do
+            v <- HashMap.lookup n namedRaw
+            return (n, v)
+        matched = HashMap.fromList $ fromPositional ++ fromNamed
+        named = HashMap.difference namedRaw (HashMap.fromList fromNamed)
 
--- | Loose cast to boolean.
+-- * Marshalling from Haskell to 'GVal'
 --
--- Numeric zero, empty strings, empty lists, empty objects, 'Null', and boolean
--- 'False' are considered falsy, anything else (including functions) is
--- considered true-ish.
-toBoolean :: GVal m -> Bool
-toBoolean = asBoolean
-
--- | Dynamically cast to a function.
--- This yields 'Just' a 'Function' if the value is a function, 'Nothing' if
--- it's not.
-toFunction :: GVal m -> Maybe (Function m)
-toFunction = asFunction
+-- | Types that implement conversion to 'GVal'.
+class ToGVal m a where
+    toGVal :: a -> GVal m
 
--- | Turn a 'Function' into a 'GVal'
-fromFunction :: Function m -> GVal m
-fromFunction f =
-    def
-        { asHtml = html ""
-        , asText = ""
-        , asBoolean = True
-        , isNull = False
-        , asFunction = Just f
-        }
+-- | Trivial instance for 'GVal' itself.
+instance ToGVal m (GVal m) where
+    toGVal = id
 
+-- | 'Nothing' becomes NULL, 'Just' unwraps.
 instance ToGVal m v => ToGVal m (Maybe v) where
     toGVal Nothing = def
     toGVal (Just x) = toGVal x
 
+-- | Haskell lists become list-like 'GVal's
 instance ToGVal m v => ToGVal m [v] where
     toGVal xs = helper (Prelude.map toGVal xs)
         where
@@ -228,6 +180,7 @@
                     , length = Just $ Prelude.length xs
                     }
 
+-- | 'HashMap' of 'Text' becomes a dictionary-like 'GVal'
 instance ToGVal m v => ToGVal m (HashMap Text v) where
     toGVal xs = helper (HashMap.map toGVal xs)
         where
@@ -272,12 +225,15 @@
             , isNull = False
             }
 
+-- | Silly helper function, needed to bypass the default 'Show' instance of
+-- 'Scientific' in order to make integral 'Scientific's look like integers.
 scientificToText :: Scientific -> Text
 scientificToText x =
     Text.pack $ case floatingOrInteger x of
         Left x -> show x
         Right x -> show x
 
+-- | Booleans render as 1 or empty string, and otherwise behave as expected.
 instance ToGVal m Bool where
     toGVal x =
         def
@@ -288,6 +244,10 @@
             , isNull = False
             }
 
+-- | 'String' -> 'GVal' conversion uses the 'IsString' class; because 'String'
+-- is an alias for '[Char]', there is also a 'ToGVal' instance for 'String',
+-- but it marshals strings as lists of characters, i.e., calling 'toGVal' on
+-- a string produces a list of characters on the 'GVal' side.
 instance IsString (GVal m) where
     fromString x =
         def
@@ -299,6 +259,10 @@
             , length = Just . Prelude.length $ x
             }
 
+-- | Single characters are treated as length-1 'Text's.
+instance ToGVal m Char where
+    toGVal = toGVal . Text.singleton
+
 instance ToGVal m Text where
     toGVal x =
         def
@@ -319,6 +283,19 @@
             , isNull = False
             }
 
+-- | This instance is slightly wrong; the 'asBoolean', 'asNumber', and 'asText'
+-- methods all treat the HTML source as plain text. We do this to avoid parsing
+-- the HTML back into a 'Text' (and dealing with possible parser errors); the
+-- reason this instance exists at all is that we still want to be able to pass
+-- pre-rendered HTML around sometimes, and as long as we don't call any numeric
+-- or string functions on it, everything is fine. When such HTML values
+-- accidentally do get used as strings, the HTML source will bleed into the
+-- visible text, but at least this will not introduce an XSS vulnerability.
+--
+-- It is therefore recommended to avoid passing 'Html' values into templates,
+-- and also to avoid calling any string functions on 'Html' values inside
+-- templates (e.g. capturing macro output and then passing it through a textual
+-- filter).
 instance ToGVal m Html where
     toGVal x =
         def
@@ -339,3 +316,83 @@
     toGVal (JSON.Null) = def
     toGVal (JSON.Array a) = toGVal $ Vector.toList a
     toGVal (JSON.Object o) = toGVal o
+
+-- | Turn a 'Function' into a 'GVal'
+fromFunction :: Function m -> GVal m
+fromFunction f =
+    def
+        { asHtml = html ""
+        , asText = ""
+        , asBoolean = True
+        , isNull = False
+        , asFunction = Just f
+        }
+
+
+-- * Inspecting 'GVal's / Marshalling 'GVal' to Haskell
+
+-- | Check if the given GVal is a list-like object
+isList :: GVal m -> Bool
+isList = isJust . asList
+
+-- | Check if the given GVal is a dictionary-like object
+isDict :: GVal m -> Bool
+isDict = isJust . asDictItems
+
+-- | Treat a 'GVal' as a flat list and look up a value by integer index.
+-- If the value is not a List, or if the index exceeds the list length,
+-- return 'Nothing'.
+lookupIndex :: Int -> GVal m -> Maybe (GVal m)
+lookupIndex = lookupIndexMay . Just
+
+-- | Helper function; look up a value by an integer index when the index may or
+-- may not be available. If no index is given, return 'Nothing'.
+lookupIndexMay :: Maybe Int -> GVal m -> Maybe (GVal m)
+lookupIndexMay i v = do
+    index <- i
+    items <- asList v
+    atMay items index
+
+-- | Strictly-typed lookup: treat value as a dictionary-like object and look
+-- up the value at a given key.
+lookupKey :: Text -> GVal m -> Maybe (GVal m)
+lookupKey k v = do
+    lf <- asLookup v
+    lf k
+
+-- | Loosely-typed lookup: try dictionary-style lookup first (treat index as
+-- a string, and container as a dictionary), if that doesn't yield anything
+-- (either because the index is not string-ish, or because the container
+-- doesn't provide dictionary-style access), try index-based lookup.
+lookupLoose :: GVal m -> GVal m -> Maybe (GVal m)
+lookupLoose k v =
+    lookupKey (asText k) v <|> lookupIndexMay (floor <$> asNumber k) v
+
+-- | Treat a 'GVal' as a dictionary and list all the keys, with no particular
+-- ordering.
+keys :: GVal m -> Maybe [Text]
+keys v = Prelude.map fst <$> asDictItems v
+
+-- | Convert a 'GVal' to a number.
+toNumber :: GVal m -> Maybe Scientific
+toNumber = asNumber
+
+-- | Convert a 'GVal' to an 'Int'.
+-- The conversion will fail when the value is not numeric, and also if
+-- it is too large to fit in an 'Int'.
+toInt :: GVal m -> Maybe Int
+toInt x = toNumber x >>= toBoundedInteger
+
+-- | Loose cast to boolean.
+--
+-- Numeric zero, empty strings, empty lists, empty objects, 'Null', and boolean
+-- 'False' are considered falsy, anything else (including functions) is
+-- considered true-ish.
+toBoolean :: GVal m -> Bool
+toBoolean = asBoolean
+
+-- | Dynamically cast to a function.
+-- This yields 'Just' a 'Function' if the value is a function, 'Nothing' if
+-- it's not.
+toFunction :: GVal m -> Maybe (Function m)
+toFunction = asFunction
diff --git a/src/Text/Ginger/Run.hs b/src/Text/Ginger/Run.hs
--- a/src/Text/Ginger/Run.hs
+++ b/src/Text/Ginger/Run.hs
@@ -6,6 +6,18 @@
 {-#LANGUAGE MultiParamTypeClasses #-}
 {-#LANGUAGE ScopedTypeVariables #-}
 -- | Execute Ginger templates in an arbitrary monad.
+--
+-- Usage example:
+--
+-- > render :: Template -> Text -> Text -> Text
+-- > render template -> username imageURL = do
+-- >    let contextLookup varName =
+-- >            case varName of
+-- >                "username" -> toGVal username
+-- >                "imageURL" -> toGVal imageURL
+-- >                _ -> def -- def for GVal is equivalent to a NULL value
+-- >        context = makeContext contextLookup
+-- >    in htmlSource $ runGinger context template
 module Text.Ginger.Run
 ( runGingerT
 , runGinger
@@ -219,19 +231,27 @@
 
 -- | Create an execution context for runGinger.
 -- The argument is a lookup function that maps top-level context keys to ginger
--- values.
+-- values. 'makeContext' is a specialized version of 'makeContextM', targeting
+-- the 'Writer' 'Html' monad (which is what is used for the non-monadic
+-- template interpreter 'runGinger').
+--
+-- The type of the lookup function may look intimidating, but in most cases,
+-- marshalling values from Haskell to Ginger is a matter of calling 'toGVal'
+-- on them, so the 'GVal (Run (Writer Html))' part can usually be ignored.
+-- See the 'Text.Ginger.GVal' module for details.
 makeContext :: (VarName -> GVal (Run (Writer Html))) -> GingerContext (Writer Html)
 makeContext l =
     makeContextM
         (return . l)
         tell
 
--- | Purely expand a Ginger template. @v@ is the type for Ginger values.
+-- | Purely expand a Ginger template. The underlying carrier monad is 'Writer'
+-- 'Html', which is used to collect the output and render it into a 'Html'
+-- value.
 runGinger :: GingerContext (Writer Html) -> Template -> Html
 runGinger context template = execWriter $ runGingerT context template
 
--- | Monadically run a Ginger template. The @m@ parameter is the carrier monad,
--- the @v@ parameter is the type for Ginger values.
+-- | Monadically run a Ginger template. The @m@ parameter is the carrier monad.
 runGingerT :: (Monad m, Functor m) => GingerContext m -> Template -> m ()
 runGingerT context tpl = runReaderT (evalStateT (runTemplate tpl) (defRunState tpl)) context
 
