diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,26 @@
 # doctemplates
 
+## 0.8.3
+
+  * Properly handle nested loops (#15).  Previously "it" was
+    always used for the variable in a loop, and in a nested loop
+    there was no way to distinguish the value of the inner
+    iteration from the value of the outer one.  Now we assign
+    the iterated value to both "it" and to the original variable
+    name (e.g. "foo.bar").  This probably has a small negative
+    performance impact.  Note that this change also affects
+    the output of the template parser:  original variable
+    names are now retained instead of being replaced by "it".
+
+  * Remove duplicate IsString constraint (#14, Mario Lang).
+
+  * Update haddocks from README (#10).
+
+  * Minor code clean-ups (#7, favonia).
+
+  * Add hsyaml >= 0.2 constraint (#6).
+
+
 ## 0.8.2
 
   * Add filters: first, rest, last, allbutlast.
diff --git a/doctemplates.cabal b/doctemplates.cabal
--- a/doctemplates.cabal
+++ b/doctemplates.cabal
@@ -1,5 +1,5 @@
 name:                doctemplates
-version:             0.8.2
+version:             0.8.3
 synopsis:            Pandoc-style document templates
 description:         This is the text templating system used by pandoc.
                      It supports variable interpolation, iteration,
@@ -32,7 +32,7 @@
                        safe,
                        text-conversions,
                        aeson,
-                       HsYAML,
+                       HsYAML >= 0.2 && < 0.3,
                        text,
                        doclayout >= 0.3 && < 0.4,
                        containers,
diff --git a/src/Text/DocTemplates.hs b/src/Text/DocTemplates.hs
--- a/src/Text/DocTemplates.hs
+++ b/src/Text/DocTemplates.hs
@@ -14,9 +14,9 @@
 
 Control structures are provided to test whether a variable has a
 non-blank value and to iterate over the items of a list. Partials—that
-is, subtemplates defined in different files—are supported. Filters can
-be used to transform the values of variables or partials. The provided
-filters make it possible to do list enumeration and tabular layout in
+is, subtemplates defined in different files—are supported. Pipes can be
+used to transform the values of variables or partials. The provided
+pipes make it possible to do list enumeration and tabular layout in
 templates.
 
 Templates are rendered to a doclayout @Doc@ (which is polymorphic in the
@@ -309,11 +309,11 @@
 
 The @~@ keyword has no effect when rendering to @Text@ or @String@.
 
-== Filters
+== Pipes
 
-A filter transforms the value of a variable or partial. Filters are
+A pipe transforms the value of a variable or partial. Pipes are
 specified using a slash (@\/@) between the variable name (or partial)
-and the filter name. Example:
+and the pipe name. Example:
 
 > $for(name)$
 > $name/uppercase$
@@ -325,13 +325,13 @@
 >
 > $employee:name()/uppercase$
 
-Filters may be chained:
+Pipes may be chained:
 
 > $for(employees/pairs)$
 > $it.key/alpha/uppercase$. $it.name$
 > $endfor$
 
-Some filters take parameters:
+Some pipes take parameters:
 
 > |----------------------|------------|
 > $for(employee)$
@@ -339,11 +339,23 @@
 > $endfor$
 > |----------------------|------------|
 
-Currently the following filters are predefined:
+Currently the following pipes are predefined:
 
 -   @pairs@: Converts a map or array to an array of maps, each with
     @key@ and @value@ fields. If the original value was an array, the
     @key@ will be the array index, starting with 1.
+
+-   @first@: Returns the first value of an array, if applied to a
+    non-empty array; otherwise returns the original value.
+
+-   @last@: Returns the last value of an array, if applied to a
+    non-empty array; otherwise returns the original value.
+
+-   @rest@: Returns all but the first value of an array, if applied to a
+    non-empty array; otherwise returns the original value.
+
+-   @allbutlast@: Returns all but the last value of an array, if applied
+    to a non-empty array; otherwise returns the original value.
 
 -   @uppercase@: Converts text to uppercase.
 
diff --git a/src/Text/DocTemplates/Internal.hs b/src/Text/DocTemplates/Internal.hs
--- a/src/Text/DocTemplates/Internal.hs
+++ b/src/Text/DocTemplates/Internal.hs
@@ -2,7 +2,6 @@
 {-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE TypeSynonymInstances #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE OverloadedStrings #-}
@@ -10,8 +9,6 @@
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE DeriveTraversable #-}
-{-# LANGUAGE DeriveFoldable #-}
-{-# LANGUAGE DeriveFunctor #-}
 
 {- |
    Module      : Text.DocTemplates.Internal
@@ -44,6 +41,7 @@
 import Control.Monad.Identity
 import qualified Control.Monad.State.Strict as S
 import Data.Char (chr, ord)
+import Data.Maybe (fromMaybe)
 import qualified Data.Text.Read as T
 import qualified Data.Text as T
 import qualified Data.Text.IO as TIO
@@ -166,15 +164,15 @@
   toVal     = SimpleVal . DL.literal
 
 instance ToContext a a => ToContext a (Doc a) where
-  toVal    = SimpleVal
+  toVal     = SimpleVal
 
 -- This is needed because otherwise the compiler tries to
 -- match on ToContext a [b], with a = b = Char, even though
 -- we don't have ToContext Char Char.  I don't understand why.
-instance {-# OVERLAPS #-} ToContext String String where
+instance {-# OVERLAPPING #-} ToContext String String where
   toVal    = SimpleVal . DL.literal
 
-instance {-# OVERLAPS #-} ToContext String (Doc String) where
+instance {-# OVERLAPPING #-} ToContext String (Doc String) where
   toVal    = SimpleVal
 
 instance ToContext a b => ToContext a [b] where
@@ -188,7 +186,7 @@
   toVal True  = SimpleVal "true"
   toVal False = NullVal
 
-instance (IsString a, TemplateTarget a) => ToContext a Value where
+instance TemplateTarget a => ToContext a Value where
   toContext x = case fromJSON x of
                   Success y -> y
                   Error _   -> mempty
@@ -217,7 +215,7 @@
 -- This is needed because otherwise the compiler tries to
 -- match on FromContext a [b], with a = b = Char, even though
 -- we don't have FromContext Char Char.  I don't understand why.
-instance {-# OVERLAPS #-} FromContext String String where
+instance {-# OVERLAPPING #-} FromContext String String where
   fromVal (SimpleVal x) = Just (DL.render Nothing x)
   fromVal _             = Nothing
 
@@ -225,7 +223,7 @@
   fromVal (ListVal  xs) = mapM fromVal xs
   fromVal x             = sequence [fromVal x]
 
-instance (IsString a, TemplateTarget a) => FromJSON (Val a) where
+instance TemplateTarget a => FromJSON (Val a) where
   parseJSON v =
     case v of
       Array vec   -> ListVal <$> mapM parseJSON (V.toList vec)
@@ -239,7 +237,7 @@
                        mapM parseJSON o
       _           -> return NullVal
 
-instance (IsString a, TemplateTarget a) => FromJSON (Context a) where
+instance TemplateTarget a => FromJSON (Context a) where
   parseJSON v = do
     val <- parseJSON v
     case val of
@@ -261,7 +259,7 @@
       Scalar _ (SBool True) -> return $ SimpleVal "true"
       _           -> return NullVal
 
-instance (IsString a, TemplateTarget a) => FromYAML (Context a) where
+instance TemplateTarget a => FromYAML (Context a) where
   parseYAML v = do
     val <- parseYAML v
     case val of
@@ -349,7 +347,7 @@
 applyPipe ToRoman val = mapText toRoman' val
   where toRoman' t =
          case T.decimal t of
-           Right (y,"") -> maybe t id (toRoman y)
+           Right (y,"") -> fromMaybe t (toRoman y)
            _            -> t
 applyPipe NoWrap val = mapDoc DL.nowrap val
 applyPipe (Block align n border) val =
@@ -424,14 +422,23 @@
 withVariable :: (Monad m, TemplateTarget a)
              => Variable -> Context a -> (Context a -> m (Doc a))
              -> m [Doc a]
-withVariable  v ctx f =
-  case applyPipes (varPipes v) $ multiLookup (varParts v) (MapVal ctx) of
+withVariable var ctx f =
+  case applyPipes (varPipes var) $ multiLookup (varParts var) (MapVal ctx) of
     NullVal     -> return mempty
-    ListVal xs  -> mapM (\iterval -> f $
-                    Context $ M.insert "it" iterval $ unContext ctx) xs
-    MapVal ctx' -> (:[]) <$> f
-                    (Context $ M.insert "it" (MapVal ctx') $ unContext ctx)
-    val' -> (:[]) <$> f (Context $ M.insert "it" val' $ unContext ctx)
+    ListVal xs  -> mapM (\iterval -> f $ setVarVal iterval) xs
+    MapVal ctx' -> (:[]) <$> f (setVarVal (MapVal ctx'))
+    val' -> (:[]) <$> f (setVarVal val')
+ where
+  setVarVal x =
+    addToContext var x $ Context $ M.insert "it" x $ unContext ctx
+  addToContext (Variable [] _) _ (Context ctx') = Context ctx'
+  addToContext (Variable (v:vs) fs) x (Context ctx') =
+    Context $ M.adjust
+              (\z -> case z of
+                       _ | null vs -> x
+                       MapVal m    ->
+                         MapVal $ addToContext (Variable vs fs) x m
+                       _ -> z) v ctx'
 
 type RenderState = S.State Int
 
@@ -448,7 +455,7 @@
 
 renderTemp :: forall a . TemplateTarget a
            => Template a -> Context a -> RenderState (Doc a)
-renderTemp (Literal t) _ = updateColumn $ t
+renderTemp (Literal t) _ = updateColumn t
 renderTemp (Interpolate v) ctx = updateColumn $ mconcat $ resolveVariable v ctx
 renderTemp (Conditional v ift elset) ctx =
   let res = resolveVariable v ctx
diff --git a/src/Text/DocTemplates/Parser.hs b/src/Text/DocTemplates/Parser.hs
--- a/src/Text/DocTemplates/Parser.hs
+++ b/src/Text/DocTemplates/Parser.hs
@@ -1,6 +1,5 @@
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE BangPatterns #-}
 {- |
    Module      : Text.DocTemplates.Parser
    Copyright   : Copyright (C) 2009-2019 John MacFarlane
@@ -24,7 +23,6 @@
 import Data.Text (Text)
 import qualified Data.Text as T
 import qualified Data.Text.Read as T
-import Data.List (isPrefixOf)
 import System.FilePath
 import Text.DocTemplates.Internal
 import qualified Text.DocLayout as DL
@@ -140,10 +138,9 @@
 
 pDirective :: (TemplateTarget a, TemplateMonad m)
            => Parser m (Template a)
-pDirective = do
-  res <- pConditional <|> pForLoop <|> pReflowToggle <|> pNested <|>
-         pInterpolate <|> pBarePartial
-  return res
+pDirective =
+  pConditional <|> pForLoop <|> pReflowToggle <|> pNested <|>
+  pInterpolate <|> pBarePartial
 
 pEnclosed :: Monad m => Parser m a -> Parser m a
 pEnclosed parser = P.try $ do
@@ -227,7 +224,7 @@
           return (y <> z)
   let contents = x <> mconcat xs
   P.updateState $ \st -> st{ nestedCol = oldNested }
-  return $ Nested $ contents
+  return $ Nested contents
 
 pForLoop :: (TemplateTarget a, TemplateMonad m) => Parser m (Template a)
 pForLoop = do
@@ -235,31 +232,14 @@
   -- if newline after the "for", then a newline after "endfor" will be swallowed
   pInside $ do
     multiline <- P.option False $ skipEndline >> return True
-    contents <- changeToIt v <$> pTemplate
+    contents <- pTemplate
     sep <- P.option mempty $
              do pEnclosed (P.string "sep")
                 when multiline $ P.option () skipEndline
-                changeToIt v <$> pTemplate
+                pTemplate
     pEnclosed (P.string "endfor")
     when multiline $ P.option () skipEndline
     return $ Iterate v contents sep
-
-changeToIt :: (Semigroup a) => Variable -> Template a -> Template a
-changeToIt v = go
- where
-  go (Interpolate w) = Interpolate (reletter v w)
-  go (Conditional w t1 t2) = Conditional (reletter v w)
-        (changeToIt v t1) (changeToIt v t2)
-  go (Iterate w t1 t2) = Iterate (reletter v w)
-        (changeToIt v t1) (changeToIt v t2)
-  go (Concat t1 t2) = changeToIt v t1 <> changeToIt v t2
-  go (Partial fs t) = Partial fs t  -- don't reletter inside partial
-  go (Nested t) = Nested (go t)
-  go x = x
-  reletter (Variable vs _fs) (Variable ws gs) =
-    if vs `isPrefixOf` ws
-       then Variable ("it" : drop (length vs) ws) gs
-       else Variable ws gs
 
 pInterpolate :: (TemplateTarget a, TemplateMonad m)
              => Parser m (Template a)
diff --git a/test/nested-loop.test b/test/nested-loop.test
new file mode 100644
--- /dev/null
+++ b/test/nested-loop.test
@@ -0,0 +1,41 @@
+{
+  "pages": [
+    {
+      "subpages": [
+        {
+          "slug": "subpage-1"
+        },
+        {
+          "slug": "subpage-2"
+        }
+      ],
+      "slug": "page-1"
+    },
+    {
+      "subpages": [
+        {
+          "slug": "subpage-1"
+        },
+        {
+          "slug": "subpage-2"
+        }
+      ],
+      "slug": "page-2"
+    }
+  ]
+}
+.
+$-- see #15
+$for(pages)$
+/$pages.slug$
+$for(pages.subpages)$
+  /$pages.slug$/$pages.subpages.slug$
+$endfor$
+$endfor$
+.
+/page-1
+  /page-1/subpage-1
+  /page-1/subpage-2
+/page-2
+  /page-2/subpage-1
+  /page-2/subpage-2
