diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+# 0.3.0.0
+
+  * Fix renaming bug that was inveigling folds with lambdas
+  * Add `nf` builtin
+  * Add deduplication builtin (`~.`)
+  * Add anchor ability to print multiple streams
+  * Make `Option` a functor
+  * Add `Witherable` class, `:?` (mapMaybe)
+  * Allow file `@include`s (crude library capability)
+  * Fix typos in manpage
+
 # 0.2.1.0
 
   * Add `fp` builtin
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@
 # SHOCK & AWE
 
 ```
-ls -l | ja '(+)|0 {ix>1}{`5:i}'
+ls -l | ja '(+)|0 {ix>1}{`5:}'
 ```
 
 ```
@@ -32,6 +32,26 @@
     ja ',[1.0-x%y] {ix>1}{`5:} {ix>1}{`11:}' -F,
 ```
 
+## Rosetta
+
+Replace
+
+```awk
+NF == 1 && $1 != "}" {
+  haveversion[$1] = 1
+}
+END {
+  for (i in haveversion)
+    printf "have-%s = yes\n", i
+}
+```
+
+with
+
+```
+(sprintf 'have-%s = yes')" ~.{nf=1 & `1 != '}'}{`1}
+```
+
 # Documentation
 
 See the [guide](https://vmchale.github.io/jacinda/), which contains a tutorial
@@ -59,7 +79,6 @@
   * Typeclasses are not documented
   * Type system is questionable
   * Postfix `:f` and `:i` are handled poorly
-  * File imports/includes
   * Various bugs in evaluation with regular expressions
 
 Intentionally missing features:
@@ -67,12 +86,13 @@
   * No loops
   * No conditionals
 
-The latter in particular I may add if necessary
+The latter in particular I may add if necessary/requested
 
 # Further Advantages
 
   * [Rust's regular expressions](https://docs.rs/regex/)
     - extensively documented with Unicode support
+  * Deduplicate builtin
 
 # PERFORMANCE
 
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -9,9 +9,9 @@
 import qualified Paths_jacinda        as P
 import           System.IO            (stdin)
 
-data Command = TypeCheck !FilePath
-             | Run !FilePath !(Maybe FilePath)
-             | Expr !BSL.ByteString !(Maybe FilePath) !(Maybe BS.ByteString)
+data Command = TypeCheck !FilePath ![FilePath]
+             | Run !FilePath !(Maybe FilePath) ![FilePath]
+             | Expr !BSL.ByteString !(Maybe FilePath) !(Maybe BS.ByteString) ![FilePath]
              | Eval !BSL.ByteString
 
 jacFile :: Parser FilePath
@@ -47,11 +47,22 @@
     <> command "run" (info runP (progDesc "Run from file")))
     <|> exprP
     where
-        tcP = TypeCheck <$> jacFile
-        runP = Run <$> jacFile <*> inpFile
-        exprP = Expr <$> jacExpr <*> inpFile <*> jacFs
+        tcP = TypeCheck <$> jacFile <*> includes
+        runP = Run <$> jacFile <*> inpFile <*> includes
+        exprP = Expr <$> jacExpr <*> inpFile <*> jacFs <*> includes
         eP = Eval <$> jacExpr
 
+includes :: Parser [FilePath]
+includes = many $ strOption
+    (metavar "DIR"
+    <> long "include"
+    <> short 'I'
+    <> dirCompletions)
+
+dirCompletions :: HasCompleter f => Mod f a
+dirCompletions = completer . bashCompleter $ "directory"
+
+
 wrapper :: ParserInfo Command
 wrapper = info (helper <*> versionMod <*> commandP)
     (fullDesc
@@ -65,9 +76,9 @@
 main = run =<< execParser wrapper
 
 run :: Command -> IO ()
-run (TypeCheck fp)         = tcIO =<< BSL.readFile fp
-run (Run fp Nothing)       = do { contents <- BSL.readFile fp ; runOnHandle contents Nothing stdin }
-run (Run fp (Just dat))    = do { contents <- BSL.readFile fp ; runOnFile contents Nothing dat }
-run (Expr eb Nothing fs)   = runOnHandle eb fs stdin
-run (Expr eb (Just fp) fs) = runOnFile eb fs fp
-run (Eval e)               = print (exprEval e)
+run (TypeCheck fp is)         = tcIO is =<< BSL.readFile fp
+run (Run fp Nothing is)       = do { contents <- BSL.readFile fp ; runOnHandle is contents Nothing stdin }
+run (Run fp (Just dat) is)    = do { contents <- BSL.readFile fp ; runOnFile is contents Nothing dat }
+run (Expr eb Nothing fs is)   = runOnHandle is eb fs stdin
+run (Expr eb (Just fp) fs is) = runOnFile is eb fs fp
+run (Eval e)                  = print (exprEval e)
diff --git a/bench/Bench.hs b/bench/Bench.hs
new file mode 100644
--- /dev/null
+++ b/bench/Bench.hs
@@ -0,0 +1,24 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main (main) where
+
+import           Control.DeepSeq (NFData (..))
+import           Criterion.Main
+import           Jacinda.AST
+import           Jacinda.File
+
+main :: IO ()
+main =
+    defaultMain [ bgroup "eval"
+                      [ bench "exprEval" $ nf exprEval "[x+' '+y]|'' split '01-23-1987' /-/"
+                      ]
+                ]
+
+instance NFData (E a) where
+    rnf (StrLit _ str)  = rnf str
+    rnf (IntLit _ i)    = rnf i
+    rnf (BoolLit _ b)   = rnf b
+    rnf (FloatLit _ f)  = rnf f
+    rnf (Arr _ es)      = rnf es
+    rnf (Tup _ es)      = rnf es
+    rnf (OptionVal _ e) = rnf e
diff --git a/doc/guide.pdf b/doc/guide.pdf
Binary files a/doc/guide.pdf and b/doc/guide.pdf differ
diff --git a/examples/bc.jac b/examples/bc.jac
new file mode 100644
--- /dev/null
+++ b/examples/bc.jac
@@ -0,0 +1,2 @@
+{. {#t}{#`0}
+#"$0
diff --git a/examples/chess.jac b/examples/chess.jac
new file mode 100644
--- /dev/null
+++ b/examples/chess.jac
@@ -0,0 +1,23 @@
+{. fn sum(x) :=
+  {. (+)|0 x;
+
+{. fn count(x) :=
+  {. sum [:1"x;
+
+fn count(x) ≔
+  (+)|0 [:1"x;
+
+fn processLine(b) ≔
+  let
+    val pre := (split b /-/).1
+    val l := #pre
+    val res := substr pre (l-1) l
+  in res end;
+
+let
+  val iStream := processLine"$0
+  val white := count ((='1') #. iStream)
+  val black := count ((='0') #. iStream)
+  val draw := count ((='2') #. iStream)
+  val total := white + black + draw
+in (total . white . black . draw) end
diff --git a/examples/excess.jac b/examples/excess.jac
new file mode 100644
--- /dev/null
+++ b/examples/excess.jac
@@ -0,0 +1,2 @@
+:set fs := /,/;
+{ix>1 & `11 = 'Unweighted' & `12 = 'All causes'}{(`1.`2.`3:i-`6:i)}
diff --git a/examples/lc.jac b/examples/lc.jac
new file mode 100644
--- /dev/null
+++ b/examples/lc.jac
@@ -0,0 +1,3 @@
+{. (+)|0 {/./}{1} (count nonblank lines)
+(+)|0 {#t}{1}
+{. (+)|0 #"$0 (count bytes in file)
diff --git a/examples/lib.jac b/examples/lib.jac
new file mode 100644
--- /dev/null
+++ b/examples/lib.jac
@@ -0,0 +1,10 @@
+fn sum(x) :=
+  (+)|0 x;
+
+fn map(f, x) :=
+  f"x;
+
+fn count (x) :=
+  sum (map ([:1) x);
+
+sum $1:i
diff --git a/examples/line.jac b/examples/line.jac
new file mode 100644
--- /dev/null
+++ b/examples/line.jac
@@ -0,0 +1,1 @@
+{|sprintf '%i: %s' (ix.`0)}
diff --git a/examples/mve.jac b/examples/mve.jac
new file mode 100644
--- /dev/null
+++ b/examples/mve.jac
@@ -0,0 +1,8 @@
+fn colmap(str) :=
+  let val p := split str /.*\^/ in p end;
+
+{. option 0 (->2) (match '---------^' /.*\^/)
+
+let
+  val col := {%/^[\t-]*\^/}{`0}
+in col end
diff --git a/examples/path.jac b/examples/path.jac
new file mode 100644
--- /dev/null
+++ b/examples/path.jac
@@ -0,0 +1,5 @@
+{. echo $PATH | ja run examples/path.jac
+fn path(x) :=
+  ([x+'\n'+y])|'' (splitc x ':');
+
+path"$0
diff --git a/examples/path2.jac b/examples/path2.jac
new file mode 100644
--- /dev/null
+++ b/examples/path2.jac
@@ -0,0 +1,8 @@
+{. echo $PATH | ja run examples/path.jac
+
+@include'lib/string.jac'
+
+fn path(x) :=
+  intercalate '\n' (splitc x ':');
+
+path"$0
diff --git a/examples/span.jac b/examples/span.jac
new file mode 100644
--- /dev/null
+++ b/examples/span.jac
@@ -0,0 +1,9 @@
+:set fs:=/\|/;
+
+fn printSpan(str) :=
+  let
+    val p := match str /\^+/
+    val str := option '(none)' (sprintf '%i-%i') p
+  in str end;
+
+printSpan"{% / *\^+/}{`2}
diff --git a/examples/span2.jac b/examples/span2.jac
new file mode 100644
--- /dev/null
+++ b/examples/span2.jac
@@ -0,0 +1,9 @@
+:set fs:=/\|/;
+
+fn printSpan(str) :=
+  let
+    val p := match str /\^+/
+    val str := (sprintf '%i-%i')"p
+  in str end;
+
+printSpan:?{% /\|/}{`2}
diff --git a/examples/sumLs.jac b/examples/sumLs.jac
new file mode 100644
--- /dev/null
+++ b/examples/sumLs.jac
@@ -0,0 +1,2 @@
+{. ls -l | ja ...
+{ix>1}{`5:i}
diff --git a/examples/tags.jac b/examples/tags.jac
new file mode 100644
--- /dev/null
+++ b/examples/tags.jac
@@ -0,0 +1,10 @@
+fn mkEx(s) :=
+  '/^' + s + '$/;';
+
+fn processStr(s) :=
+  let
+    val line := split s /[ \(]+/
+    val outLine := sprintf '%s\t%s\t%s' (line.2 . fp . mkEx s)
+  in outLine end;
+
+processStr"{%/fn +[[:lower:]][[:latin:]]*.*:=/}{`0}
diff --git a/jacinda.cabal b/jacinda.cabal
--- a/jacinda.cabal
+++ b/jacinda.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.0
 name:               jacinda
-version:            0.2.1.0
+version:            0.3.0.0
 license:            AGPL-3
 license-file:       COPYING
 maintainer:         vamchale@gmail.com
@@ -18,7 +18,11 @@
     man/ja.1
     doc/guide.pdf
     test/examples/*.jac
+    examples/*.jac
+
+data-files:
     lib/*.jac
+    prelude/*.jac
 
 source-repository head
     type:     git
@@ -48,8 +52,11 @@
         Jacinda.Backend.Normalize
         Jacinda.Backend.TreeWalk
         Jacinda.Backend.Printf
+        Jacinda.Include
         Data.List.Ext
         Data.Vector.Ext
+        Paths_jacinda
+    autogen-modules:  Paths_jacinda
 
     default-language: Haskell2010
     ghc-options:      -Wall -O2
@@ -58,15 +65,18 @@
         bytestring >=0.11.0.0,
         text,
         prettyprinter >=1.7.0,
-        containers,
+        containers >=0.6.0.1,
         array,
         mtl,
         transformers,
-        regex-rure,
+        regex-rure >=0.1.2.0,
         microlens,
+        directory,
+        filepath,
         microlens-mtl,
         vector,
-        recursion >=1.0.0.0
+        recursion >=1.0.0.0,
+        split
 
     if !flag(cross)
         build-tool-depends: alex:alex, happy:happy
@@ -124,6 +134,33 @@
         tasty,
         tasty-hunit,
         bytestring
+
+    if impl(ghc >=8.0)
+        ghc-options:
+            -Wincomplete-uni-patterns -Wincomplete-record-updates
+            -Wredundant-constraints -Widentities
+
+    if impl(ghc >=8.4)
+        ghc-options: -Wmissing-export-lists
+
+    if impl(ghc >=8.2)
+        ghc-options: -Wcpp-undef
+
+    if impl(ghc >=8.10)
+        ghc-options: -Wunused-packages
+
+
+benchmark jacinda-bench
+    type:             exitcode-stdio-1.0
+    main-is:          Bench.hs
+    hs-source-dirs:   bench
+    default-language: Haskell2010
+    ghc-options:      -Wall
+    build-depends:
+        base,
+        criterion,
+        jacinda-lib,
+        deepseq
 
     if impl(ghc >=8.0)
         ghc-options:
diff --git a/lib/example.jac b/lib/example.jac
deleted file mode 100644
--- a/lib/example.jac
+++ /dev/null
@@ -1,10 +0,0 @@
-fn sum(x) :=
-  (+)|0 x;
-
-fn map(f, x) :=
-  f"x;
-
-fn count (x) :=
-  sum (map ([:1) x);
-
-sum $1:i
diff --git a/lib/maybe.jac b/lib/maybe.jac
new file mode 100644
--- /dev/null
+++ b/lib/maybe.jac
@@ -0,0 +1,5 @@
+fn isJust(x) :=
+  option #f ([:#t) x;
+
+fn isNothing(x) :=
+  option #t ([:#f) x;
diff --git a/lib/string.jac b/lib/string.jac
new file mode 100644
--- /dev/null
+++ b/lib/string.jac
@@ -0,0 +1,5 @@
+fn intercalate(b, strs) :=
+  [x+b+y]|'' strs;
+
+fn replace(re, t, str) :=
+  [x+t+y]|'' (split str re);
diff --git a/lib/tag.jac b/lib/tag.jac
new file mode 100644
--- /dev/null
+++ b/lib/tag.jac
@@ -0,0 +1,2 @@
+fn mkEx(s) :=
+  '/^' + s + '$/;';
diff --git a/man/ja.1 b/man/ja.1
--- a/man/ja.1
+++ b/man/ja.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pandoc 2.17
+.\" Automatically generated by Pandoc 2.17.0.1
 .\"
 .TH "ja (1)" "" "" "" ""
 .hy
@@ -9,11 +9,11 @@
 .PP
 ja run src.jac -i data.txt
 .PP
-cat FILE1 FILE2 | ja `#\[lq]$0'
+cat FILE1 FILE2 | ja \[aq]#\[lq]$0\[cq]
 .PP
 ja tc script.jac
 .PP
-ja e `11.67*1.2'
+ja e \[aq]11.67*1.2\[cq]
 .SH DESCRIPTION
 .PP
 \f[B]Jacinda\f[R] is a data stream processing language \[`a] la AWK.
@@ -31,6 +31,9 @@
 .TP
 \f[B]-V\f[R] \f[B]--version\f[R]
 Display version information
+.TP
+\f[B]-I\f[R] \f[B]--include\f[R]
+Include directory for imports
 .SH LANGUAGE
 .SS REGEX
 .PP
@@ -62,10 +65,13 @@
 a -> b -> a
 .TP
 \f[B]#.\f[R] Binary operator: filter
-(a -> Bool) -> Stream a -> Stream a
+Witherable f :=> (a -> Bool) -> f a -> f a
 .TP
 \f[B]\[rs].\f[R] Binary operator: prior
 (a -> a -> a) -> Stream a -> Stream a
+.TP
+\f[B]\[ti].\f[R] Unary deduplication (stream)
+Eq a :=> Stream a -> Stream a
 .PP
 \f[B]max\f[R] Maximum of two values
 .PP
@@ -107,8 +113,16 @@
 \f[B]match\f[R]
 Str -> Regex -> Option (Int .
 Int)
+.TP
+\f[B]:?\f[R] mapMaybe
+Witherable f :=> (a -> Option b) -> f a -> f b
+.TP
+\f[B].?\f[R] catMaybes
+Witherable f :=> f (Option a) -> f a
 .PP
 \f[B]fp\f[R] Filename
+.PP
+\f[B]nf\f[R] Number of fields
 .SS SYNTAX
 .PP
 \f[B]\[ga]n\f[R] nth field
@@ -133,13 +147,12 @@
 \f[B]->n\f[R] Get the nth element of a tuple
 .PP
 \f[B]{.\f[R] Line comment
+.PP
+\f[B]\[at]include\[aq]/path/file.jac\[cq]\f[R] File include
 .SH BUGS
 .PP
 Please report any bugs you may come across to
 https://github.com/vmchale/jacinda/issues
-.SS Limitations
-.PP
-Note that \f[C]Option\f[R] is not implemented as a functor.
 .SH COPYRIGHT
 .PP
 Copyright 2021-2022.
diff --git a/prelude/fn.jac b/prelude/fn.jac
new file mode 100644
--- /dev/null
+++ b/prelude/fn.jac
@@ -0,0 +1,26 @@
+fn sum(x) :=
+  (+)|0 x;
+
+fn drop(n, str) :=
+  let val l := #str
+    in substr str n l end;
+
+fn take(n, str) :=
+  substr str 0 n;
+
+{. rounds down on .5
+fn round(x) := |. (x+0.5);
+
+fn itostring() :=
+  sprintf '%i';
+
+fn any(p, xs) :=
+  (||)|#f p"xs;
+
+fn all(p, xs) :=
+  (&)|#t p"xs;
+
+fn id(x) := x;
+
+fn fromMaybe(a, x) :=
+  option a [x] x;
diff --git a/src/Jacinda/AST.hs b/src/Jacinda/AST.hs
--- a/src/Jacinda/AST.hs
+++ b/src/Jacinda/AST.hs
@@ -31,9 +31,18 @@
 import qualified Data.Vector        as V
 import           GHC.Generics       (Generic)
 import           Intern.Name
-import           Prettyprinter      (Doc, Pretty (..), braces, brackets, encloseSep, flatAlt, group, parens, (<+>))
+import           Prettyprinter      (Doc, Pretty (..), braces, brackets, concatWith, encloseSep, flatAlt, group, hardline, indent, parens, tupled, (<+>))
 import           Regex.Rure         (RurePtr)
 
+infixr 6 <#>
+infixr 6 <##>
+
+(<#>) :: Doc a -> Doc a -> Doc a
+(<#>) x y = x <> hardline <> y
+
+(<##>) :: Doc a -> Doc a -> Doc a
+(<##>) x y = x <> hardline <> hardline <> y
+
 -- kind
 data K = Star
        | KArr K K
@@ -47,6 +56,7 @@
         | TyVec
         | TyBool
         | TyOption
+        | TyUnit
         -- TODO: tyRegex
         -- TODO: convert float to int
         deriving (Eq, Ord)
@@ -80,6 +90,7 @@
     pretty TyDate    = "Date"
     pretty TyVec     = "List"
     pretty TyOption  = "Optional"
+    pretty TyUnit    = "𝟙"
 
 instance Pretty (T a) where
     pretty (TyB _ b)        = pretty b
@@ -104,6 +115,8 @@
          | Floor
          | Ceiling
          | Some
+         | Dedup
+         | CatMaybes
          deriving (Eq)
 
 instance Pretty BUn where
@@ -118,6 +131,8 @@
     pretty Ceiling    = "ceil"
     pretty Parse      = ":"
     pretty Some       = "Some"
+    pretty Dedup      = "~."
+    pretty CatMaybes  = ".?"
 
 -- ternary
 data BTer = ZipW
@@ -125,14 +140,16 @@
           | Scan
           | Substr
           | Option
+          | Captures
           deriving (Eq)
 
 instance Pretty BTer where
-    pretty ZipW   = ","
-    pretty Fold   = "|"
-    pretty Scan   = "^"
-    pretty Substr = "substr"
-    pretty Option = "option"
+    pretty ZipW     = ","
+    pretty Fold     = "|"
+    pretty Scan     = "^"
+    pretty Substr   = "substr"
+    pretty Option   = "option"
+    pretty Captures = "~*"
 
 -- builtin
 data BBin = Plus
@@ -158,6 +175,7 @@
           | Filter
           | Sprintf
           | Match
+          | MapMaybe
           -- TODO: floor functions, sqrt, sin, cos, exp. (power)
           deriving (Eq)
 
@@ -185,6 +203,7 @@
     pretty Splitc     = "splitc"
     pretty Sprintf    = "sprintf"
     pretty Match      = "match"
+    pretty MapMaybe   = ":?"
 
 data DfnVar = X | Y deriving (Eq)
 
@@ -194,6 +213,7 @@
 
 -- 0-ary
 data N = Ix
+       | Nf
        | None
        | Fp
        deriving (Eq)
@@ -227,6 +247,7 @@
          | ResVar { eLoc :: a, dfnVar :: DfnVar }
          | RegexCompiled RurePtr -- holds compiled regex (after normalization)
          | Arr { eLoc :: a, elems :: V.Vector (E a) }
+         | Anchor { eLoc :: a, eAnchored :: [E a] }
          | Paren { eLoc :: a, eExpr :: E a }
          | OptionVal { eLoc :: a, eMaybe :: Maybe (E a) }
          -- TODO: regex literal
@@ -263,6 +284,7 @@
             | ResVarF a DfnVar
             | RegexCompiledF RurePtr
             | ArrF a (V.Vector x)
+            | AnchorF a [x]
             | ParenF a x
             | OptionValF a (Maybe x)
             deriving (Generic, Functor, Foldable, Traversable)
@@ -271,60 +293,63 @@
 
 instance Pretty N where
     pretty Ix   = "ix"
+    pretty Nf   = "nf"
     pretty None = "None"
     pretty Fp   = "fp"
 
 instance Pretty (E a) where
-    pretty (Column _ i)                                            = "$" <> pretty i
-    pretty AllColumn{}                                             = "$0"
-    pretty (IParseCol _ i)                                         = "$" <> pretty i <> ":i"
-    pretty (FParseCol _ i)                                         = "$" <> pretty i <> ":f"
-    pretty AllField{}                                              = "`0"
-    pretty (Field _ i)                                             = "`" <> pretty i
-    pretty (EApp _ (EApp _ (BBuiltin _ Prior) e) e')               = pretty e <> "\\." <+> pretty e'
-    pretty (EApp _ (EApp _ (BBuiltin _ Max) e) e')                 = "max" <+> pretty e <+> pretty e'
-    pretty (EApp _ (EApp _ (BBuiltin _ Min) e) e')                 = "min" <+> pretty e <+> pretty e'
-    pretty (EApp _ (EApp _ (BBuiltin _ Split) e) e')               = "split" <+> pretty e <+> pretty e'
-    pretty (EApp _ (EApp _ (BBuiltin _ Splitc) e) e')              = "splitc" <+> pretty e <+> pretty e'
-    pretty (EApp _ (EApp _ (BBuiltin _ Match) e) e')               = "match" <+> pretty e <+> pretty e'
-    pretty (EApp _ (EApp _ (BBuiltin _ Sprintf) e) e')             = "sprintf" <+> pretty e <+> pretty e'
-    pretty (EApp _ (EApp _ (BBuiltin _ Map) e) e')                 = pretty e <> "\"" <> pretty e'
-    pretty (EApp _ (EApp _ (BBuiltin _ b) e) e')                   = pretty e <+> pretty b <+> pretty e'
-    pretty (EApp _ (BBuiltin _ b) e)                               = parens (pretty e <> pretty b)
-    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ Fold) e) e') e'')   = pretty e <> "|" <> pretty e' <+> pretty e''
-    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ Scan) e) e') e'')   = pretty e <> "^" <> pretty e' <+> pretty e''
-    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ ZipW) op) e') e'')  = "," <> pretty op <+> pretty e' <+> pretty e''
-    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ Substr) e) e') e'') = "substr" <+> pretty e <+> pretty e' <+> pretty e''
-    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ Option) e) e') e'') = "option" <+> pretty e <+> pretty e' <+> pretty e''
-    pretty (EApp _ (UBuiltin _ (At i)) e)                          = pretty e <> "." <> pretty i
-    pretty (EApp _ (UBuiltin _ (Select i)) e)                      = pretty e <> "->" <> pretty i
-    pretty (EApp _ (UBuiltin _ IParse) e')                         = pretty e' <> ":i"
-    pretty (EApp _ (UBuiltin _ FParse) e')                         = pretty e' <> ":f"
-    pretty (EApp _ (UBuiltin _ Parse) e')                          = pretty e' <> ":"
-    pretty (EApp _ e@UBuiltin{} e')                                = pretty e <> pretty e'
-    pretty (EApp _ e e')                                           = pretty e <+> pretty e'
-    pretty (Var _ n)                                               = pretty n
-    pretty (IntLit _ i)                                            = pretty i
-    pretty (RegexLit _ rr)                                         = "/" <> pretty (decodeUtf8 rr) <> "/"
-    pretty (FloatLit _ f)                                          = pretty f
-    pretty (BoolLit _ True)                                        = "#t"
-    pretty (BoolLit _ False)                                       = "#f"
-    pretty (BBuiltin _ b)                                          = parens (pretty b)
-    pretty (UBuiltin _ u)                                          = pretty u
-    pretty (StrLit _ bstr)                                         = pretty (decodeUtf8 bstr)
-    pretty (ResVar _ x)                                            = pretty x
-    pretty (Tup _ es)                                              = jacTup es
-    pretty (Lam _ n e)                                             = parens ("λ" <> pretty n <> "." <+> pretty e)
-    pretty (Dfn _ e)                                               = brackets (pretty e)
-    pretty (Guarded _ p e)                                         = braces (pretty p) <> braces (pretty e)
-    pretty (Implicit _ e)                                          = braces ("|" <+> pretty e)
-    pretty (NBuiltin _ n)                                          = pretty n
-    pretty RegexCompiled{}                                         = error "Nonsense."
-    pretty (Let _ (n, b) e)                                        = "let" <+> "val" <+> pretty n <+> ":=" <+> pretty b <+> "in" <+> pretty e <+> "end"
-    pretty (Paren _ e)                                             = parens (pretty e)
-    pretty (Arr _ es)                                              = tupledByFunky "," (V.toList $ pretty <$> es)
-    pretty (OptionVal _ (Just e))                                  = "Some" <+> pretty e
-    pretty (OptionVal _ Nothing)                                   = "None"
+    pretty (Column _ i)                                              = "$" <> pretty i
+    pretty AllColumn{}                                               = "$0"
+    pretty (IParseCol _ i)                                           = "$" <> pretty i <> ":i"
+    pretty (FParseCol _ i)                                           = "$" <> pretty i <> ":f"
+    pretty AllField{}                                                = "`0"
+    pretty (Field _ i)                                               = "`" <> pretty i
+    pretty (EApp _ (EApp _ (BBuiltin _ Prior) e) e')                 = pretty e <> "\\." <+> pretty e'
+    pretty (EApp _ (EApp _ (BBuiltin _ Max) e) e')                   = "max" <+> pretty e <+> pretty e'
+    pretty (EApp _ (EApp _ (BBuiltin _ Min) e) e')                   = "min" <+> pretty e <+> pretty e'
+    pretty (EApp _ (EApp _ (BBuiltin _ Split) e) e')                 = "split" <+> pretty e <+> pretty e'
+    pretty (EApp _ (EApp _ (BBuiltin _ Splitc) e) e')                = "splitc" <+> pretty e <+> pretty e'
+    pretty (EApp _ (EApp _ (BBuiltin _ Match) e) e')                 = "match" <+> pretty e <+> pretty e'
+    pretty (EApp _ (EApp _ (BBuiltin _ Sprintf) e) e')               = "sprintf" <+> pretty e <+> pretty e'
+    pretty (EApp _ (EApp _ (BBuiltin _ Map) e) e')                   = pretty e <> "\"" <> pretty e'
+    pretty (EApp _ (EApp _ (BBuiltin _ b) e) e')                     = pretty e <+> pretty b <+> pretty e'
+    pretty (EApp _ (BBuiltin _ b) e)                                 = parens (pretty e <> pretty b)
+    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ Fold) e) e') e'')     = pretty e <> "|" <> pretty e' <+> pretty e''
+    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ Scan) e) e') e'')     = pretty e <> "^" <> pretty e' <+> pretty e''
+    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ ZipW) op) e') e'')    = "," <> pretty op <+> pretty e' <+> pretty e''
+    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ Substr) e) e') e'')   = "substr" <+> pretty e <+> pretty e' <+> pretty e''
+    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ Option) e) e') e'')   = "option" <+> pretty e <+> pretty e' <+> pretty e''
+    pretty (EApp _ (EApp _ (EApp _ (TBuiltin _ Captures) e) e') e'') = pretty e <+> "~*" <+> pretty e' <+> pretty e''
+    pretty (EApp _ (UBuiltin _ (At i)) e)                            = pretty e <> "." <> pretty i
+    pretty (EApp _ (UBuiltin _ (Select i)) e)                        = pretty e <> "->" <> pretty i
+    pretty (EApp _ (UBuiltin _ IParse) e')                           = pretty e' <> ":i"
+    pretty (EApp _ (UBuiltin _ FParse) e')                           = pretty e' <> ":f"
+    pretty (EApp _ (UBuiltin _ Parse) e')                            = pretty e' <> ":"
+    pretty (EApp _ e@UBuiltin{} e')                                  = pretty e <> pretty e'
+    pretty (EApp _ e e')                                             = pretty e <+> pretty e'
+    pretty (Var _ n)                                                 = pretty n
+    pretty (IntLit _ i)                                              = pretty i
+    pretty (RegexLit _ rr)                                           = "/" <> pretty (decodeUtf8 rr) <> "/"
+    pretty (FloatLit _ f)                                            = pretty f
+    pretty (BoolLit _ True)                                          = "#t"
+    pretty (BoolLit _ False)                                         = "#f"
+    pretty (BBuiltin _ b)                                            = parens (pretty b)
+    pretty (UBuiltin _ u)                                            = pretty u
+    pretty (StrLit _ bstr)                                           = pretty (decodeUtf8 bstr)
+    pretty (ResVar _ x)                                              = pretty x
+    pretty (Tup _ es)                                                = jacTup es
+    pretty (Lam _ n e)                                               = parens ("λ" <> pretty n <> "." <+> pretty e)
+    pretty (Dfn _ e)                                                 = brackets (pretty e)
+    pretty (Guarded _ p e)                                           = braces (pretty p) <> braces (pretty e)
+    pretty (Implicit _ e)                                            = braces ("|" <+> pretty e)
+    pretty (NBuiltin _ n)                                            = pretty n
+    pretty RegexCompiled{}                                           = error "Nonsense."
+    pretty (Let _ (n, b) e)                                          = "let" <+> "val" <+> pretty n <+> ":=" <+> pretty b <+> "in" <+> pretty e <+> "end"
+    pretty (Paren _ e)                                               = parens (pretty e)
+    pretty (Arr _ es)                                                = tupledByFunky "," (V.toList $ pretty <$> es)
+    pretty (Anchor _ es)                                             = "&" <> tupledBy "." (pretty <$> es)
+    pretty (OptionVal _ (Just e))                                    = "Some" <+> pretty e
+    pretty (OptionVal _ Nothing)                                     = "None"
 
 instance Show (E a) where
     show = show . pretty
@@ -370,6 +395,7 @@
        | Foldable
        | IsPrintf
        | HasField Int (T K)
+       | Witherable
        -- TODO: witherable
        deriving (Eq, Ord)
 
@@ -383,6 +409,7 @@
     pretty Foldable        = "Foldable"
     pretty IsPrintf        = "Printf"
     pretty (HasField i ty) = "HasField" <+> pretty i <+> "~" <+> pretty ty
+    pretty Witherable      = "Witherable"
 
 instance Show C where
     show = show . pretty
@@ -392,8 +419,18 @@
          | FunDecl (Name a) [Name a] (E a)
          deriving (Functor)
 
+instance Pretty (D a) where
+    pretty (SetFS bs)       = ":set" <+> "/" <> pretty (decodeUtf8 bs) <> "/"
+    pretty (FunDecl n ns e) = "fn" <+> pretty n <> tupled (pretty <$> ns) <+> ":=" <#> indent 2 (pretty e <> ";")
+
 -- TODO: fun decls (type decls)
 data Program a = Program { decls :: [D a], expr :: E a } deriving (Functor)
+
+instance Pretty (Program a) where
+    pretty (Program ds e) = concatWith (<##>) (pretty <$> ds) <##> pretty e
+
+instance Show (Program a) where
+    show = show . pretty
 
 getFS :: Program a -> Maybe BS.ByteString
 getFS (Program ds _) = listToMaybe (concatMap go ds) where
diff --git a/src/Jacinda/Backend/Normalize.hs b/src/Jacinda/Backend/Normalize.hs
--- a/src/Jacinda/Backend/Normalize.hs
+++ b/src/Jacinda/Backend/Normalize.hs
@@ -14,10 +14,14 @@
                                  , parseAsF
                                  , the
                                  , asTup
+                                 -- * Monad
+                                 , runEvalM
+                                 , eNorm
                                  ) where
 
-import           Control.Monad.State.Strict (State, evalState, gets, modify)
+import           Control.Monad.State.Strict (State, evalState, gets, modify, runState)
 import           Control.Recursion          (cata, embed)
+import           Data.Bifunctor             (second)
 import qualified Data.ByteString            as BS
 import qualified Data.ByteString.Char8      as ASCII
 import           Data.Foldable              (traverse_)
@@ -104,17 +108,24 @@
 mkLetCtx :: Int -> LetCtx
 mkLetCtx i = LetCtx IM.empty (Renames i IM.empty)
 
+hoistEvalM :: Int -> EvalM a -> (a, Int)
+hoistEvalM i = second (max_ . renames_) . flip runState (mkLetCtx i)
+
+runEvalM :: Int
+         -> EvalM a
+         -> a
+runEvalM i = flip evalState (mkLetCtx i)
+
 eClosed :: Int
         -> E (T K)
         -> E (T K)
-eClosed i = flip evalState (mkLetCtx i) . eNorm
+eClosed i = runEvalM i . eNorm
 
 closedProgram :: Int
               -> Program (T K)
               -> E (T K)
-closedProgram i (Program ds e) = flip evalState (mkLetCtx i) $
-    traverse_ processDecl ds *>
-    eNorm e
+closedProgram i (Program ds e) = runEvalM i $
+    traverse_ processDecl ds *> eNorm e
 
 processDecl :: D (T K)
             -> EvalM ()
@@ -127,6 +138,7 @@
 asTup Nothing                = OptionVal undefined Nothing
 asTup (Just (RureMatch s e)) = OptionVal undefined (Just $ Tup undefined (mkI . fromIntegral <$> [s, e]))
 
+-- don't need to rename op because it's being used in a map, can't affect etc.
 applyUn :: E (T K)
         -> E (T K)
         -> EvalM (E (T K))
@@ -139,7 +151,8 @@
         -> E (T K)
         -> E (T K)
         -> EvalM (E (T K))
-applyOp op e e' = eNorm (EApp undefined (EApp undefined op e) e') -- TODO: undefined??
+applyOp op@BBuiltin{}  e e' = eNorm (EApp undefined (EApp undefined op e) e') -- short-circuit if not a lambda, don't need rename
+applyOp op e e'             = do { op' <- renameE op ; eNorm (EApp undefined (EApp undefined op' e) e') }
 
 foldE :: E (T K)
       -> E (T K)
@@ -169,6 +182,7 @@
 eNorm e@BBuiltin{}    = pure e
 eNorm e@TBuiltin{}    = pure e
 eNorm (Tup tys es)    = Tup tys <$> traverse eNorm es
+eNorm (Anchor ty es)  = Anchor ty <$> traverse eNorm es
 eNorm e@NBuiltin{}    = pure e
 eNorm (EApp ty op@BBuiltin{} e) = EApp ty op <$> eNorm e
 eNorm (EApp ty (EApp ty' op@(BBuiltin _ Matches) e) e') = do
@@ -195,7 +209,7 @@
     eI <- eNorm e
     eI' <- eNorm e'
     pure $ case (eI, eI') of
-        (StrLit _ s, StrLit _ s')       -> StrLit tyStr (s <> s')
+        (StrLit _ s, StrLit _ s')       -> StrLit tyStr (s <> s') -- TODO: copy?
         (RegexLit _ rr, RegexLit _ rr') -> RegexLit tyStr (rr <> rr')
         _                               -> EApp ty (EApp ty' op eI) eI'
 eNorm (EApp ty (EApp ty' op@(BBuiltin (TyArr _ (TyB _ TyInteger) _) Max) e) e') = do
@@ -383,6 +397,7 @@
         _                           -> EApp ty0 (EApp ty1 op eI) eI'
 eNorm (EApp _ (EApp _ (UBuiltin _ Const) e) _) = eNorm e
 eNorm (EApp ty op@(UBuiltin _ Const) e) = EApp ty op <$> eNorm e
+eNorm (EApp ty op@(UBuiltin _ Dedup) e) = EApp ty op <$> eNorm e
 eNorm (EApp ty op@(UBuiltin _ (At i)) e) = do
     eI <- eNorm e
     pure $ case eI of
@@ -418,9 +433,11 @@
     pure $ case eI of
         (StrLit _ str) -> parseAsEInt str
         _              -> EApp ty op eI
-eNorm (EApp ty op@(UBuiltin _ Some) e) = do
+eNorm (EApp ty (UBuiltin _ Some) e) = do
     eI <- eNorm e
     pure $ OptionVal ty (Just eI)
+-- catMaybes only works for streams atm
+eNorm (EApp ty op@(UBuiltin _ CatMaybes) e) = EApp ty op <$> eNorm e
 eNorm Dfn{} = desugar
 eNorm ResVar{} = desugar
 eNorm (Let _ (Name _ (Unique i) _, b) e) = do
@@ -445,6 +462,13 @@
     pure $ case (e0', e1', e2') of
         (StrLit _ str, IntLit _ i, IntLit _ j) -> mkStr (substr str (fromIntegral i) (fromIntegral j))
         _                                      -> EApp ty0 (EApp ty1 (EApp ty2 (TBuiltin ty3 Substr) e0') e1') e2'
+eNorm (EApp ty0 (EApp ty1 (EApp ty2 op@(TBuiltin _ Captures) e0) e1) e2) = do
+    e0' <- eNorm e0
+    e1' <- eNorm e1
+    e2' <- eNorm e2
+    pure $ case (e0', e1', e2') of
+        (StrLit _ str, IntLit _ ix, RegexCompiled re) -> OptionVal (tyOpt tyStr) (mkStr <$> findCapture re str (fromIntegral ix))
+        _                                             -> EApp ty0 (EApp ty1 (EApp ty2 op e0') e1') e2'
 eNorm (EApp ty0 (EApp ty1 (EApp ty2 op@(TBuiltin _ Option) e0) e1) e2) = do
     e0' <- eNorm e0
     e1' <- eNorm e1
@@ -471,6 +495,12 @@
     case y' of
         Arr _ es -> Arr undefined <$> traverse (applyUn x') es -- TODO: undefined?
         _        -> pure $ EApp ty0 (EApp ty1 op x') y'
+eNorm (EApp ty0 (EApp ty1 op@(BBuiltin (TyArr _ _ (TyArr _ _ (TyApp _ (TyB _ TyOption) _))) Map) x) y) = do
+    x' <- eNorm x
+    y' <- eNorm y
+    case y' of
+        OptionVal _ e -> OptionVal undefined <$> traverse (applyUn x') e -- TODO: undefined?
+        _             -> pure $ EApp ty0 (EApp ty1 op x') y'
 eNorm (EApp ty0 (EApp ty1 (EApp ty2 op@(TBuiltin (TyArr _ _ (TyArr _ _ (TyArr _ (TyApp _ (TyB _ TyVec) _) _))) Fold) f) x) y) = do
     f' <- eNorm f
     x' <- eNorm x
@@ -493,6 +523,7 @@
         -- _                     -> pure $ EApp ty0 (EApp ty1 (EApp ty2 op f') x') y'
 eNorm (EApp ty0 (EApp ty1 (EApp ty2 op@TBuiltin{} f) x) y) = EApp ty0 <$> (EApp ty1 <$> (EApp ty2 op <$> eNorm f) <*> eNorm x) <*> eNorm y
 eNorm (EApp ty0 (EApp ty1 op@(BBuiltin _ Map) x) y) = EApp ty0 <$> (EApp ty1 op <$> eNorm x) <*> eNorm y
+eNorm (EApp ty0 (EApp ty1 op@(BBuiltin _ MapMaybe) x) y) = EApp ty0 <$> (EApp ty1 op <$> eNorm x) <*> eNorm y
 eNorm (EApp ty0 (EApp ty1 op@(BBuiltin _ Prior) x) y) = EApp ty0 <$> (EApp ty1 op <$> eNorm x) <*> eNorm y
 eNorm (EApp ty0 (EApp ty1 op@(BBuiltin _ Filter) x) y) = EApp ty0 <$> (EApp ty1 op <$> eNorm x) <*> eNorm y
 -- FIXME: this will almost surely run into trouble; if the above pattern matches
diff --git a/src/Jacinda/Backend/TreeWalk.hs b/src/Jacinda/Backend/TreeWalk.hs
--- a/src/Jacinda/Backend/TreeWalk.hs
+++ b/src/Jacinda/Backend/TreeWalk.hs
@@ -7,9 +7,11 @@
 import           Control.Exception         (Exception, throw)
 import           Control.Recursion         (cata, embed)
 import qualified Data.ByteString           as BS
+import           Data.Containers.ListUtils (nubIntOn, nubOrdOn)
 import           Data.Foldable             (foldl', traverse_)
-import           Data.List                 (scanl')
+import           Data.List                 (scanl', transpose)
 import           Data.List.Ext
+import           Data.Maybe                (mapMaybe)
 import           Data.Semigroup            ((<>))
 import qualified Data.Vector               as V
 import           Jacinda.AST
@@ -65,6 +67,15 @@
 asArr (Arr _ es) = es
 asArr _          = noRes
 
+asOpt :: E a -> Maybe (E a)
+asOpt (OptionVal _ e) = e
+asOpt _               = noRes
+
+-- just shove some big number into the renamer and hope it doesn't clash (bad,
+-- hack, this is why we got kicked out of the garden of Eden)
+reprehensible :: Int
+reprehensible = (maxBound :: Int) `div` 2
+
 -- TODO: do I want to interleave state w/ eNorm or w/e
 
 withFp :: FileBS -> E (T K) -> E (T K)
@@ -86,6 +97,7 @@
     go op@BBuiltin{} = op
     go op@UBuiltin{} = op
     go op@TBuiltin{} = op
+    go (NBuiltin _ Nf) = mkI (fromIntegral $ V.length ctx)
     go (EApp ty op@BBuiltin{} e) = EApp ty op (go e)
     go (NBuiltin _ Ix) = mkI (fromIntegral ix)
     go (NBuiltin _ Fp) = mkStr fp
@@ -121,6 +133,11 @@
         let eI = asRegex (go e)
             eI' = asStr (go e')
         in asTup (find' eI eI')
+    go (EApp _ (EApp _ (EApp _ (TBuiltin _ Captures) e0) e1) e2) =
+        let e0' = asStr (go e0)
+            e1' = asInt (go e1)
+            e2' = asRegex (go e2)
+            in OptionVal (tyOpt tyStr) (mkStr <$> findCapture e2' e0' (fromIntegral e1'))
     go (EApp _ (EApp _ (BBuiltin (TyArr _ (TyB _ TyInteger) _) Plus) e) e') =
         let eI = asInt (go e)
             eI' = asInt (go e')
@@ -136,6 +153,7 @@
     go (EApp _ (EApp _ (BBuiltin (TyArr _ (TyB _ TyStr) _) Plus) e) e') =
         let eI = asStr (go e)
             eI' = asStr (go e')
+            -- TODO: copy??
             in mkStr (eI <> eI')
     go (EApp _ (EApp _ (BBuiltin (TyArr _ (TyB _ TyStr) _) Eq) e) e') =
         let eI = asStr (go e)
@@ -267,6 +285,12 @@
         in Arr undefined (applyUn' x' <$> y')
         where applyUn' :: E (T K) -> E (T K) -> E (T K)
               applyUn' e e' = go (EApp undefined e e')
+    go (EApp _ (EApp _ (BBuiltin (TyArr _ _ (TyArr _ _ (TyApp _ (TyB _ TyOption) _))) Map) x) y) =
+        let x' = go x
+            y' = asOpt (go y)
+        in OptionVal undefined (applyUn' x' <$> y')
+        where applyUn' :: E (T K) -> E (T K) -> E (T K)
+              applyUn' e e' = go (EApp undefined e e')
     go (EApp _ (EApp _ (EApp _ (TBuiltin (TyArr _ _ (TyArr _ _ (TyArr _ (TyApp _ (TyB _ TyVec) _) _))) Fold) f) seed) xs) =
         let f' = go f
             seed' = go seed
@@ -280,7 +304,7 @@
         -> E (T K)
         -> E (T K)
         -> E (T K)
-applyOp op e e' = eClosed undefined (EApp undefined (EApp undefined op e) e') -- FIXME: undefined is ??
+applyOp op e e' = eClosed reprehensible (EApp undefined (EApp undefined op e) e') -- FIXME: undefined is ??
 
 atField :: RurePtr
         -> Int
@@ -296,7 +320,7 @@
         -> E (T K)
 applyUn unOp e =
     case eLoc unOp of
-        TyArr _ _ res -> eClosed undefined (EApp res unOp e)
+        TyArr _ _ res -> eClosed reprehensible (EApp res unOp e)
         _             -> error "Internal error?"
 
 -- | Turn an expression representing a stream into a stream of expressions (using line as context)
@@ -322,6 +346,11 @@
 ir fp re (EApp _ (EApp _ (BBuiltin _ Filter) op) stream) =
     let op' = compileR (withFp fp op)
         in filter (asBool . applyUn op') . ir fp re stream
+ir fp re (EApp _ (EApp _ (BBuiltin _ MapMaybe) op) stream) =
+    let op' = compileR (withFp fp op)
+        in mapMaybe (asOpt . applyUn op') . ir fp re stream
+ir fp re (EApp _ (UBuiltin _ CatMaybes) stream) =
+    mapMaybe asOpt . ir fp re stream
 ir fp re (EApp _ (EApp _ (BBuiltin _ Prior) op) stream) = prior (applyOp (withFp fp op)) . ir fp re stream
 ir fp re (EApp _ (EApp _ (EApp _ (TBuiltin _ ZipW) op) streaml) streamr) = \lineStream ->
     let
@@ -330,6 +359,14 @@
     in zipWith (applyOp (withFp fp op)) irl irr
 ir fp re (EApp _ (EApp _ (EApp _ (TBuiltin _ Scan) op) seed) xs) =
     scanl' (applyOp (withFp fp op)) seed . ir fp re xs
+ir fp re (EApp _ (UBuiltin (TyArr _ (TyApp _ _ (TyB _ TyStr)) _) Dedup) e) =
+    nubOrdOn asStr . ir fp re e
+ir fp re (EApp _ (UBuiltin (TyArr _ (TyApp _ _ (TyB _ TyInteger)) _) Dedup) e) =
+    nubIntOn (fromIntegral . asInt) . ir fp re e
+ir fp re (EApp _ (UBuiltin (TyArr _ (TyApp _ _ (TyB _ TyFloat)) _) Dedup) e) =
+    nubIntOn (fromEnum . asFloat) . ir fp re e
+ir fp re (EApp _ (UBuiltin (TyArr _ (TyApp _ _ (TyB _ TyBool)) _) Dedup) e) =
+    nubIntOn (fromEnum . asBool) . ir fp re e
 
 -- | Output stream that prints each entry (expression)
 printStream :: [E (T K)] -> IO ()
@@ -355,18 +392,21 @@
 eWith :: FileBS -> RurePtr -> E (T K) -> [BS.ByteString] -> E (T K)
 eWith fp re (EApp _ (EApp _ (EApp _ (TBuiltin (TyArr _ _ (TyArr _ _ (TyArr _ (TyApp _ (TyB _ TyStream) _) _))) Fold) op) seed) stream) = foldWithCtx fp re op seed stream
 eWith fp re (EApp ty e0 e1)                                                                                                            = \bs -> eClosed undefined (EApp ty (eWith fp re e0 bs) (eWith fp re e1 bs))
-eWith _ _ e@BBuiltin{}                                                                                                                = const e
-eWith _ _ e@UBuiltin{}                                                                                                                = const e
-eWith _ _ e@TBuiltin{}                                                                                                                = const e
-eWith _ _ e@StrLit{}                                                                                                                  = const e
-eWith _ _ e@FloatLit{}                                                                                                                = const e
-eWith _ _ e@IntLit{}                                                                                                                  = const e
-eWith _ _ e@BoolLit{}                                                                                                                 = const e
+eWith _ _ e@BBuiltin{}                                                                                                                 = const e
+eWith _ _ e@UBuiltin{}                                                                                                                 = const e
+eWith _ _ e@TBuiltin{}                                                                                                                 = const e
+eWith _ _ e@StrLit{}                                                                                                                   = const e
+eWith _ _ e@FloatLit{}                                                                                                                 = const e
+eWith _ _ e@IntLit{}                                                                                                                   = const e
+eWith _ _ e@BoolLit{}                                                                                                                  = const e
 eWith fp re (Tup ty es)                                                                                                                = \bs -> Tup ty ((\e -> eWith fp re e bs) <$> es)
 eWith fp re (OptionVal ty e)                                                                                                           = \bs -> OptionVal ty ((\eϵ -> eWith fp re eϵ bs) <$> e)
 -- TODO: rewrite tuple-of-folds as fold-of-tuples ... "compile" to E (T K) -> E (T K)
 -- OR "compile" to [(Int, E (T K)] -> ...
 
+takeConcatMap :: (a -> [b]) -> [a] -> [b]
+takeConcatMap f = concat . transpose . fmap f
+
 -- | Given an expression, turn it into a function which will process the file.
 fileProcessor :: FileBS
               -> RurePtr
@@ -383,20 +423,29 @@
     printStream $ fmap (parseAsEInt . atField re i) inp
 fileProcessor _ re (FParseCol _ i) = Right $ \inp -> do
     printStream $ fmap (parseAsF . atField re i) inp
-fileProcessor fp re e@Guarded{} = Right $ \inp -> do
+fileProcessor fp re e@Guarded{} = Right $ \inp ->
     printStream $ ir fp re e inp
-fileProcessor fp re e@Implicit{} = Right $ \inp -> do
+fileProcessor fp re e@Implicit{} = Right $ \inp ->
     printStream $ ir fp re e inp
-fileProcessor fp re e@(EApp _ (EApp _ (BBuiltin _ Filter) _) _) = Right $ \inp -> do
+fileProcessor fp re e@(EApp _ (EApp _ (BBuiltin _ Filter) _) _) = Right $ \inp ->
     printStream $ ir fp re e inp
-fileProcessor fp re e@(EApp _ (EApp _ (BBuiltin (TyArr _ _ (TyArr _ _ (TyApp _ (TyB _ TyStream) _))) Map) _) _) = Right $ \inp -> do
+-- at the moment, catMaybes only works on streams
+fileProcessor fp re e@(EApp _ (UBuiltin _ CatMaybes) _) = Right $ \inp ->
     printStream $ ir fp re e inp
-fileProcessor fp re e@(EApp _ (EApp _ (BBuiltin _ Prior) _) _) = Right $ \inp -> do
+fileProcessor fp re e@(EApp _ (EApp _ (BBuiltin (TyArr _ _ (TyArr _ _ (TyApp _ (TyB _ TyStream) _))) Map) _) _) = Right $ \inp ->
     printStream $ ir fp re e inp
-fileProcessor fp re e@(EApp _ (EApp _ (EApp _ (TBuiltin _ Scan) _) _) _) = Right $ \inp -> do
+fileProcessor fp re e@(EApp _ (EApp _ (BBuiltin (TyArr _ _ (TyArr _ _ (TyApp _ (TyB _ TyStream) _))) MapMaybe) _) _) = Right $ \inp ->
     printStream $ ir fp re e inp
-fileProcessor fp re e@(EApp _ (EApp _ (EApp _ (TBuiltin _ ZipW) _) _) _) = Right $ \inp -> do
+fileProcessor fp re e@(EApp _ (EApp _ (BBuiltin _ Prior) _) _) = Right $ \inp ->
     printStream $ ir fp re e inp
+fileProcessor fp re e@(EApp _ (EApp _ (EApp _ (TBuiltin _ Scan) _) _) _) = Right $ \inp ->
+    printStream $ ir fp re e inp
+fileProcessor fp re e@(EApp _ (EApp _ (EApp _ (TBuiltin _ ZipW) _) _) _) = Right $ \inp ->
+    printStream $ ir fp re e inp
+fileProcessor fp re e@(EApp _ (UBuiltin _ Dedup) _) = Right $ \inp ->
+    printStream $ ir fp re e inp
+fileProcessor fp re (Anchor _ es) = Right $ \inp ->
+    printStream $ takeConcatMap (\e -> ir fp re e inp) es
 fileProcessor _ _ Var{} = error "Internal error?"
 fileProcessor _ _ e@IntLit{} = Right $ const (print e)
 fileProcessor _ _ e@BoolLit{} = Right $ const (print e)
diff --git a/src/Jacinda/File.hs b/src/Jacinda/File.hs
--- a/src/Jacinda/File.hs
+++ b/src/Jacinda/File.hs
@@ -1,5 +1,4 @@
-module Jacinda.File ( tyCheck
-                    , tcIO
+module Jacinda.File ( tcIO
                     , tySrc
                     , runOnHandle
                     , runOnFile
@@ -9,15 +8,19 @@
 import           Control.Applicative        ((<|>))
 import           Control.Exception          (Exception, throw, throwIO)
 import           Control.Monad              ((<=<))
+import           Control.Monad.IO.Class     (liftIO)
+import           Control.Monad.State.Strict (StateT, get, put, runStateT)
 import           Data.Bifunctor             (second)
 import qualified Data.ByteString            as BS
 import qualified Data.ByteString.Char8      as ASCII
 import qualified Data.ByteString.Lazy       as BSL
 import qualified Data.ByteString.Lazy.Char8 as ASCIIL
-import           Data.Functor               (void)
+import           Data.Functor               (void, ($>))
+import           Data.Tuple                 (swap)
 import           Jacinda.AST
 import           Jacinda.Backend.Normalize
 import           Jacinda.Backend.TreeWalk
+import           Jacinda.Include
 import           Jacinda.Lexer
 import           Jacinda.Parser
 import           Jacinda.Parser.Rewrite
@@ -27,9 +30,33 @@
 import           Regex.Rure                 (RurePtr)
 import           System.IO                  (Handle)
 
+parseLib :: [FilePath] -> FilePath -> StateT AlexUserState IO [D AlexPosn]
+parseLib incls fp = do
+    contents <- liftIO $ BSL.readFile =<< resolveImport incls fp
+    st <- get
+    case parseLibWithCtx contents st of
+        Left err              -> liftIO (throwIO err)
+        Right (st', ([], ds)) -> put st' $> (rewriteD <$> ds)
+        Right (st', (is, ds)) -> do { put st' ; dss <- traverse (parseLib incls) is ; pure (concat dss ++ ds) }
+
+parseE :: [FilePath] -> BSL.ByteString -> StateT AlexUserState IO (Program AlexPosn)
+parseE incls bs = do
+    st <- get
+    case parseWithCtx bs st of
+        Left err -> liftIO $ throwIO err
+        Right (st', (is, Program ds e)) -> do
+            put st'
+            dss <- traverse (parseLib incls) is
+            pure $ Program (concat dss ++ fmap rewriteD ds) (rewriteE e)
+
+-- | Parse + rename (decls)
+parseEWithMax :: [FilePath] -> BSL.ByteString -> IO (Program AlexPosn, Int)
+parseEWithMax incls bsl = uncurry renamePGlobal . swap . second fst3 <$> runStateT (parseE incls bsl) alexInitUserState
+    where fst3 (x, _, _) = x
+
 -- | Parse + rename (globally)
 parseWithMax' :: BSL.ByteString -> Either (ParseError AlexPosn) (Program AlexPosn, Int)
-parseWithMax' = fmap (uncurry renamePGlobal . second rewriteProgram) . parseWithMax
+parseWithMax' = fmap (uncurry renamePGlobal . second (rewriteProgram . snd)) . parseWithMax
 
 exprEval :: BSL.ByteString -> E (T K)
 exprEval src =
@@ -43,41 +70,39 @@
 compileFS (Just bs) = compileDefault bs
 compileFS Nothing   = defaultRurePtr
 
-runOnBytes :: FilePath
+runOnBytes :: [FilePath]
+           -> FilePath -- ^ Data file name, for @nf@
            -> BSL.ByteString -- ^ Program
            -> Maybe BS.ByteString -- ^ Field separator
            -> BSL.ByteString
            -> IO ()
-runOnBytes fp src cliFS contents =
-    case parseWithMax' src of
-        Left err -> throwIO err
-        Right (ast, m) -> do
-            (typed, i) <- yeetIO $ runTypeM m (tyProgram ast)
-            cont <- yeetIO $ runJac (ASCII.pack fp) (compileFS (cliFS <|> getFS ast)) i typed
-            cont $ fmap BSL.toStrict (ASCIIL.lines contents)
-            -- see: BSL.split, BSL.splitWith
+runOnBytes incls fp src cliFS contents = do
+    incls' <- defaultIncludes <*> pure incls
+    (ast, m) <- parseEWithMax incls' src
+    (typed, i) <- yeetIO $ runTypeM m (tyProgram ast)
+    cont <- yeetIO $ runJac (ASCII.pack fp) (compileFS (cliFS <|> getFS ast)) i typed
+    cont $ fmap BSL.toStrict (ASCIIL.lines contents)
+    -- see: BSL.split, BSL.splitWith
 
-runOnHandle :: BSL.ByteString -- ^ Program
+runOnHandle :: [FilePath]
+            -> BSL.ByteString -- ^ Program
             -> Maybe BS.ByteString -- ^ Field separator
             -> Handle
             -> IO ()
-runOnHandle src cliFS = runOnBytes "(runOnBytes)" src cliFS <=< BSL.hGetContents
+runOnHandle is src cliFS = runOnBytes is "(runOnBytes)" src cliFS <=< BSL.hGetContents
 
-runOnFile :: BSL.ByteString
+runOnFile :: [FilePath]
+          -> BSL.ByteString
           -> Maybe BS.ByteString
           -> FilePath
           -> IO ()
-runOnFile e fs fp = runOnBytes fp e fs =<< BSL.readFile fp
-
-tcIO :: BSL.ByteString -> IO ()
-tcIO = yeetIO . tyCheck
+runOnFile is e fs fp = runOnBytes is fp e fs =<< BSL.readFile fp
 
--- | Typecheck an expression
-tyCheck :: BSL.ByteString -> Either (Error AlexPosn) ()
-tyCheck src =
-    case parseWithMax' src of
-        Right (ast, m) -> void $ runTypeM m (tyProgram ast)
-        Left err       -> throw err
+tcIO :: [FilePath] -> BSL.ByteString -> IO ()
+tcIO incls src = do
+    incls' <- defaultIncludes <*> pure incls
+    (ast, m) <- parseEWithMax incls' src
+    yeetIO $ void $ runTypeM m (tyProgram ast)
 
 tySrc :: BSL.ByteString -> T K
 tySrc src =
diff --git a/src/Jacinda/Include.hs b/src/Jacinda/Include.hs
new file mode 100644
--- /dev/null
+++ b/src/Jacinda/Include.hs
@@ -0,0 +1,36 @@
+module Jacinda.Include ( defaultIncludes
+                       , resolveImport
+                       ) where
+
+import           Control.Exception  (Exception, throwIO)
+import           Control.Monad      (filterM)
+import           Data.List.Split    (splitWhen)
+import           Data.Maybe         (listToMaybe)
+import           Paths_jacinda      (getDataDir)
+import           System.Directory   (doesFileExist)
+import           System.Environment (lookupEnv)
+import           System.FilePath    ((</>))
+
+data ImportError = FileNotFound !FilePath ![FilePath] deriving (Show)
+
+instance Exception ImportError where
+
+defaultIncludes :: IO ([FilePath] -> [FilePath])
+defaultIncludes = do
+    path <- jacPath
+    d <- getDataDir
+    pure $ (d:) . (++path)
+
+-- | Parsed @JAC_PATH@
+jacPath :: IO [FilePath]
+jacPath = maybe [] splitEnv <$> lookupEnv "JAC_PATH"
+
+splitEnv :: String -> [FilePath]
+splitEnv = splitWhen (== ':')
+
+resolveImport :: [FilePath] -- ^ Places to look
+              -> FilePath
+              -> IO FilePath
+resolveImport incl fp =
+    maybe (throwIO $ FileNotFound fp incl) pure . listToMaybe
+        =<< (filterM doesFileExist . fmap (</> fp) $ incl)
diff --git a/src/Jacinda/Lexer.x b/src/Jacinda/Lexer.x
--- a/src/Jacinda/Lexer.x
+++ b/src/Jacinda/Lexer.x
@@ -95,6 +95,7 @@
         "||"                     { mkSym OrTok }
         "("                      { mkSym LParen }
         ")"                      { mkSym RParen }
+        "&("                     { mkSym LAnchor }
         "{%"                     { mkSym LBracePercent }
         "{|"                     { mkSym LBraceBar }
         "]"                      { mkSym RSqBracket `andBegin` 0 }
@@ -111,6 +112,10 @@
         \\                       { mkSym Backslash }
         "|`"                     { mkSym CeilSym }
         "|."                     { mkSym FloorSym }
+        "~."                     { mkSym DedupTok }
+        ".?"                     { mkSym CatMaybesTok }
+        ":?"                     { mkSym MapMaybeTok }
+        "~*"                     { mkSym CapTok }
 
         in                       { mkKw KwIn }
         let                      { mkKw KwLet }
@@ -118,9 +123,11 @@
         end                      { mkKw KwEnd }
         :set                     { mkKw KwSet }
         fn                       { mkKw KwFn }
+        "@include"               { mkKw KwInclude }
 
         fs                       { mkRes VarFs }
         ix                       { mkRes VarIx }
+        nf                       { mkRes VarNf }
         -- TODO: does this uncover an alex bug?
         -- ⍳                        { mkRes VarIx }
         -- ¨                        { mkSym Quot }
@@ -236,6 +243,7 @@
          | LBrace
          | RBrace
          | LParen
+         | LAnchor
          | RParen
          | LSqBracket
          | RSqBracket
@@ -264,6 +272,10 @@
          | FilterTok
          | FloorSym
          | CeilSym
+         | DedupTok
+         | CatMaybesTok
+         | MapMaybeTok
+         | CapTok
 
 instance Pretty Sym where
     pretty PlusTok       = "+"
@@ -287,6 +299,7 @@
     pretty OrTok         = "||"
     pretty LParen        = "("
     pretty RParen        = ")"
+    pretty LAnchor       = "&("
     pretty LSqBracket    = "["
     pretty RSqBracket    = "]"
     pretty Tilde         = "~"
@@ -305,6 +318,10 @@
     pretty FilterTok     = "#."
     pretty FloorSym      = "|."
     pretty CeilSym       = "|`"
+    pretty DedupTok      = "~."
+    pretty CatMaybesTok  = ".?"
+    pretty MapMaybeTok   = ":?"
+    pretty CapTok        = "~*"
 
 data Keyword = KwLet
              | KwIn
@@ -312,6 +329,7 @@
              | KwEnd
              | KwSet
              | KwFn
+             | KwInclude
 
 -- | Reserved/special variables
 data Var = VarX
@@ -320,23 +338,26 @@
          | VarIx
          | VarMin
          | VarMax
+         | VarNf
 
 instance Pretty Var where
     pretty VarX     = "x"
     pretty VarY     = "y"
     pretty VarFs    = "fs"
     pretty VarIx    = "ix"
+    pretty VarNf    = "nf"
     pretty VarMin   = "min"
     pretty VarMax   = "max"
     -- TODO: exp, log, sqrt, floor ...
 
 instance Pretty Keyword where
-    pretty KwLet = "let"
-    pretty KwIn  = "in"
-    pretty KwVal = "val"
-    pretty KwEnd = "end"
-    pretty KwSet = ":set"
-    pretty KwFn  = "fn"
+    pretty KwLet     = "let"
+    pretty KwIn      = "in"
+    pretty KwVal     = "val"
+    pretty KwEnd     = "end"
+    pretty KwSet     = ":set"
+    pretty KwFn      = "fn"
+    pretty KwInclude = "@include"
 
 data Builtin = BuiltinIParse
              | BuiltinFParse
diff --git a/src/Jacinda/Parser.y b/src/Jacinda/Parser.y
--- a/src/Jacinda/Parser.y
+++ b/src/Jacinda/Parser.y
@@ -2,9 +2,13 @@
     {-# LANGUAGE OverloadedStrings #-}
     module Jacinda.Parser ( parse
                           , parseWithMax
-                          , parseWithCtx
                           , parseWithInitCtx
+                          , parseWithCtx
+                          , parseLibWithCtx
                           , ParseError (..)
+                          -- * Type synonyms
+                          , File
+                          , Library
                           ) where
 
 import Control.Exception (Exception)
@@ -12,6 +16,7 @@
 import Control.Monad.Trans.Class (lift)
 import Data.Bifunctor (first)
 import qualified Data.ByteString.Lazy as BSL
+import qualified Data.ByteString.Char8 as ASCII
 import qualified Data.Text as T
 import Data.Typeable (Typeable)
 import qualified Intern.Name as Name
@@ -22,7 +27,8 @@
 
 }
 
-%name parseP Program
+%name parseF File
+%name parseLib Library
 %tokentype { Token AlexPosn }
 %error { parseError }
 %monad { Parse } { (>>=) } { pure }
@@ -37,6 +43,7 @@
     lsqbracket { TokSym $$ LSqBracket }
     rsqbracket { TokSym $$ RSqBracket }
     lparen { TokSym $$ LParen }
+    lanchor { TokSym $$ LAnchor }
     rparen { TokSym $$ RParen }
     semicolon { TokSym $$ Semicolon }
     backslash { TokSym $$ Backslash }
@@ -54,6 +61,7 @@
     select { $$@(TokSelect _ _) }
     floorSym { TokSym $$ FloorSym }
     ceilSym { TokSym $$ CeilSym }
+    dedup { TokSym $$ DedupTok }
 
     plus { TokSym $$ PlusTok }
     minus { TokSym $$ MinusTok }
@@ -64,6 +72,9 @@
     fold { TokSym $$ FoldTok }
     caret { TokSym $$ Caret }
     quot { TokSym $$ Quot }
+    mapMaybe { TokSym $$ MapMaybeTok }
+    catMaybes { TokSym $$ CatMaybesTok }
+    capture { TokSym $$ CapTok }
 
     eq { TokSym $$ EqTok }
     neq { TokSym $$ NeqTok }
@@ -93,6 +104,7 @@
     end { TokKeyword $$ KwEnd }
     set { TokKeyword $$ KwSet }
     fn { TokKeyword $$ KwFn }
+    include { TokKeyword $$ KwInclude }
 
     x { TokResVar $$ VarX }
     y { TokResVar $$ VarY }
@@ -100,6 +112,7 @@
     min { TokResVar $$ VarMin }
     max { TokResVar $$ VarMax }
     ix { TokResVar $$ VarIx }
+    nf { TokResVar $$ VarNf }
     fs { TokResVar $$ VarFs }
 
     split { TokBuiltin $$ BuiltinSplit }
@@ -155,6 +168,7 @@
      | eq { Eq }
      | neq { Neq }
      | quot { Map }
+     | mapMaybe { MapMaybe }
      | tilde { Matches }
      | notMatch { NotMatches }
      | and { And }
@@ -174,6 +188,15 @@
   : set fs defEq rr semicolon { SetFS (BSL.toStrict $ rr $4) }
   | fn name Args defEq E semicolon { FunDecl $2 $3 $5 }
 
+Include :: { FilePath }
+        : include strLit { ASCII.unpack (strTok $2) }
+
+File :: { ([FilePath], Program AlexPosn) }
+     : many(Include) Program { (reverse $1, $2) }
+
+Library :: { Library }
+        : many(Include) many(D) { (reverse $1, reverse $2) }
+
 Program :: { Program AlexPosn }
         : many(D) E { Program (reverse $1) $2 }
 
@@ -201,11 +224,15 @@
   | y fParse { EApp $1 (UBuiltin $2 FParse) (ResVar $1 Y) }
   | column iParse { IParseCol (loc $1) (ix $1) }
   | column fParse { FParseCol (loc $1) (ix $1) }
+  | parens(iParse) { UBuiltin $1 IParse }
+  | parens(fParse) { UBuiltin $1 FParse }
+  | parens(colon) { UBuiltin $1 Parse }
   | lparen BBin rparen { BBuiltin $1 $2 }
   | lparen E BBin rparen { EApp $1 (BBuiltin $1 $3) $2 }
   | lparen BBin E rparen {% do { n <- lift $ freshName "x" ; pure (Lam $1 n (EApp $1 (EApp $1 (BBuiltin $1 $2) (Var (Name.loc n) n)) $3)) } }
   | E BBin E { EApp (eLoc $1) (EApp (eLoc $3) (BBuiltin (eLoc $1) $2) $1) $3 }
   | E fold E E { EApp (eLoc $1) (EApp (eLoc $1) (EApp $2 (TBuiltin $2 Fold) $1) $3) $4 }
+  | E capture E E { EApp (eLoc $1) (EApp (eLoc $1) (EApp $2 (TBuiltin $2 Captures) $1) $3) $4 }
   | E caret E E { EApp (eLoc $1) (EApp (eLoc $1) (EApp $2 (TBuiltin $2 Scan) $1) $3) $4 }
   | comma E E E { EApp $1 (EApp $1 (EApp $1 (TBuiltin $1 ZipW) $2) $3) $4 }
   | lbrace E rbrace braces(E) { Guarded $1 $2 $4 }
@@ -213,6 +240,7 @@
   | lbraceBar E rbrace { Implicit $1 $2 }
   | let many(Bind) in E end { mkLet $1 (reverse $2) $4 }
   | lparen sepBy(E, dot) rparen { Tup $1 (reverse $2) }
+  | lanchor sepBy(E, dot) rparen { Anchor $1 (reverse $2) }
   | E E { EApp (eLoc $1) $1 $2 }
   | tally { UBuiltin $1 Tally }
   | const { UBuiltin $1 Const }
@@ -233,8 +261,11 @@
   | ceil { UBuiltin $1 Ceiling }
   | floorSym { UBuiltin $1 Floor }
   | ceilSym { UBuiltin $1 Ceiling }
+  | dedup { UBuiltin $1 Dedup }
   | some { UBuiltin $1 Some }
+  | catMaybes { UBuiltin $1 CatMaybes }
   | ix { NBuiltin $1 Ix }
+  | nf { NBuiltin $1 Nf }
   | none { NBuiltin $1 None }
   | fp { NBuiltin $1 Fp }
   | parens(at) { UBuiltin (loc $1) (At $ ix $1) }
@@ -246,6 +277,10 @@
 
 {
 
+type File = ([FilePath], Program AlexPosn)
+
+type Library = ([FilePath], [D AlexPosn])
+
 parseError :: Token AlexPosn -> Parse a
 parseError = throwError . Unexpected
 
@@ -269,18 +304,21 @@
 
 type Parse = ExceptT (ParseError AlexPosn) Alex
 
-parse :: BSL.ByteString -> Either (ParseError AlexPosn) (Program AlexPosn)
-parse = fmap snd . runParse parseP
+parse :: BSL.ByteString -> Either (ParseError AlexPosn) File
+parse = fmap snd . runParse parseF
 
-parseWithMax :: BSL.ByteString -> Either (ParseError AlexPosn) (Int, Program AlexPosn)
+parseWithMax :: BSL.ByteString -> Either (ParseError AlexPosn) (Int, File)
 parseWithMax = fmap (first fst3) . parseWithInitCtx
     where fst3 (x, _, _) = x
 
-parseWithInitCtx :: BSL.ByteString -> Either (ParseError AlexPosn) (AlexUserState, Program AlexPosn)
+parseWithInitCtx :: BSL.ByteString -> Either (ParseError AlexPosn) (AlexUserState, File)
 parseWithInitCtx bsl = parseWithCtx bsl alexInitUserState
 
-parseWithCtx :: BSL.ByteString -> AlexUserState -> Either (ParseError AlexPosn) (AlexUserState, Program AlexPosn)
-parseWithCtx = parseWithInitSt parseP
+parseWithCtx :: BSL.ByteString -> AlexUserState -> Either (ParseError AlexPosn) (AlexUserState, File)
+parseWithCtx = parseWithInitSt parseF
+
+parseLibWithCtx :: BSL.ByteString -> AlexUserState -> Either (ParseError AlexPosn) (AlexUserState, Library)
+parseLibWithCtx = parseWithInitSt parseLib
 
 runParse :: Parse a -> BSL.ByteString -> Either (ParseError AlexPosn) (AlexUserState, a)
 runParse parser str = liftErr $ runAlexSt str (runExceptT parser)
diff --git a/src/Jacinda/Parser/Rewrite.hs b/src/Jacinda/Parser/Rewrite.hs
--- a/src/Jacinda/Parser/Rewrite.hs
+++ b/src/Jacinda/Parser/Rewrite.hs
@@ -1,6 +1,9 @@
 module Jacinda.Parser.Rewrite ( rewriteProgram
+                              , rewriteD
+                              , rewriteE
                               ) where
 
+
 import           Control.Recursion (cata, embed)
 import           Jacinda.AST
 
@@ -31,6 +34,7 @@
     a (EAppF l e0@(EApp _ (BBuiltin _ Matches) _) (EApp l1 (EApp l2 e1@(BBuiltin _ Or) e2) e3))     = EApp l1 (EApp l2 e1 (EApp l e0 e2)) e3
     a (EAppF l e0@(EApp _ (BBuiltin _ NotMatches) _) (EApp l1 (EApp l2 e1@(BBuiltin _ And) e2) e3)) = EApp l1 (EApp l2 e1 (EApp l e0 e2)) e3
     a (EAppF l e0@(EApp _ (BBuiltin _ NotMatches) _) (EApp l1 (EApp l2 e1@(BBuiltin _ Or) e2) e3))  = EApp l1 (EApp l2 e1 (EApp l e0 e2)) e3
+    a (EAppF l e0@Var{} (EApp lϵ (EApp lϵϵ e1 e2) e3))                                              = EApp l (EApp lϵ (EApp lϵϵ e0 e1) e2) e3
     -- TODO rewrite dfn
     a (EAppF l e0@Var{} (EApp lϵ e1 e2))                                                            = EApp l (EApp lϵ e0 e1) e2
     a (EAppF l e0@(BBuiltin _ Max) (EApp lϵ e1 e2))                                                 = EApp l (EApp lϵ e0 e1) e2
diff --git a/src/Jacinda/Regex.hs b/src/Jacinda/Regex.hs
--- a/src/Jacinda/Regex.hs
+++ b/src/Jacinda/Regex.hs
@@ -8,6 +8,7 @@
                      , find'
                      , compileDefault
                      , substr
+                     , findCapture
                      ) where
 
 import           Control.Exception        (Exception, throwIO)
@@ -15,16 +16,15 @@
 import qualified Data.ByteString.Internal as BS
 import           Data.Semigroup           ((<>))
 import qualified Data.Vector              as V
+import           Foreign.C.Types          (CSize)
 import           Foreign.ForeignPtr       (plusForeignPtr)
-import           Regex.Rure               (RureMatch (..), RurePtr, compile, find, isMatch, matches, mkIter, rureDefaultFlags, rureFlagDotNL)
+import           Regex.Rure               (RureMatch (..), RurePtr, captures, compile, find, findCaptures, isMatch, matches', rureDefaultFlags, rureFlagDotNL)
 import           System.IO.Unsafe         (unsafeDupablePerformIO, unsafePerformIO)
 
 -- see: https://docs.rs/regex/latest/regex/#perl-character-classes-unicode-friendly
 defaultFs :: BS.ByteString
 defaultFs = "\\s+"
 
--- also ls -l | ja '{ix>1}{`5:i}'
-
 {-# NOINLINE defaultRurePtr #-}
 defaultRurePtr :: RurePtr
 defaultRurePtr = unsafePerformIO $ yeetRureIO =<< compile genFlags defaultFs
@@ -37,6 +37,14 @@
 substr (BS.BS fp l) begin endϵ | endϵ >= begin = BS.BS (fp `plusForeignPtr` begin) (min l endϵ - begin)
                                | otherwise = "error: invalid substring indices."
 
+{-# NOINLINE findCapture #-}
+findCapture :: RurePtr -> BS.ByteString -> CSize -> Maybe BS.ByteString
+findCapture re haystack@(BS.BS fp l) ix = unsafeDupablePerformIO $ fmap go <$> findCaptures re haystack ix 0
+    where go (RureMatch s e) =
+            let e' = fromIntegral e
+                s' = fromIntegral s
+                in BS.BS (fp `plusForeignPtr` s') (e'-s')
+
 {-# NOINLINE find' #-}
 find' :: RurePtr -> BS.ByteString -> Maybe RureMatch
 find' re str = unsafeDupablePerformIO $ find re str 0
@@ -47,7 +55,7 @@
         -> V.Vector BS.ByteString
 splitBy re haystack@(BS.BS fp l) =
     (\sp -> V.fromList [BS.BS (fp `plusForeignPtr` s) (e-s) | (s, e) <- sp]) slicePairs
-    where ixes = unsafeDupablePerformIO $ do { reIptr <- mkIter re; matches reIptr haystack }
+    where ixes = unsafeDupablePerformIO $ matches' re haystack
           slicePairs = case ixes of
                 (RureMatch 0 i:rms) -> mkMiddle (fromIntegral i) rms
                 rms                 -> mkMiddle 0 rms
diff --git a/src/Jacinda/Rename.hs b/src/Jacinda/Rename.hs
--- a/src/Jacinda/Rename.hs
+++ b/src/Jacinda/Rename.hs
@@ -150,5 +150,6 @@
     Let l (n', eϵ') <$> withRenames modR (renameE e')
 renameE (Paren _ e) = renameE e
 renameE (Arr l es) = Arr l <$> traverse renameE es
+renameE (Anchor l es) = Anchor l <$> traverse renameE es
 renameE (OptionVal l e) = OptionVal l <$> traverse renameE e
 renameE e = pure e -- literals &c.
diff --git a/src/Jacinda/Ty.hs b/src/Jacinda/Ty.hs
--- a/src/Jacinda/Ty.hs
+++ b/src/Jacinda/Ty.hs
@@ -9,8 +9,9 @@
                   ) where
 
 import           Control.Exception          (Exception)
+import           Control.Monad              (forM)
 import           Control.Monad.Except       (throwError)
-import           Control.Monad.State.Strict (StateT, gets, runStateT)
+import           Control.Monad.State.Strict (StateT, gets, modify, runStateT)
 import           Data.Bifunctor             (first, second)
 import           Data.Foldable              (traverse_)
 import           Data.Functor               (void, ($>))
@@ -26,14 +27,7 @@
 import           Intern.Unique
 import           Jacinda.AST
 import           Jacinda.Ty.Const
-import           Lens.Micro                 (Lens')
-import           Lens.Micro.Mtl             (modifying)
-import           Prettyprinter              (Doc, Pretty (..), hardline, squotes, vsep, (<+>))
-
-infixr 6 <#>
-
-(<#>) :: Doc a -> Doc a -> Doc a
-(<#>) x y = x <> hardline <> y
+import           Prettyprinter              (Doc, Pretty (..), squotes, vsep, (<+>))
 
 data Error a = UnificationFailed a (T ()) (T ())
              | Doesn'tSatisfy a (T ()) C
@@ -60,10 +54,6 @@
                          , constraints :: S.Set (a, T K, T K)
                          }
 
-instance Pretty (TyState a) where
-    pretty (TyState _ _ _ _ cs) =
-        "constraints:" <#> prettyConstraints cs
-
 prettyConstraints :: S.Set (b, T a, T a) -> Doc ann
 prettyConstraints cs = vsep (prettyEq . go <$> S.toList cs) where
     go (_, x, y) = (x, y)
@@ -71,17 +61,17 @@
 prettyEq :: (T a, T a) -> Doc ann
 prettyEq (ty, ty') = pretty ty <+> "≡" <+> pretty ty'
 
-maxULens :: Lens' (TyState a) Int
-maxULens f s = fmap (\x -> s { maxU = x }) (f (maxU s))
+mapMaxU :: (Int -> Int) -> TyState a -> TyState a
+mapMaxU f (TyState u k c v cs) = TyState (f u) k c v cs
 
-classVarsLens :: Lens' (TyState a) (IM.IntMap (S.Set (C, a)))
-classVarsLens f s = fmap (\x -> s { classVars = x }) (f (classVars s))
+mapClassVars :: (IM.IntMap (S.Set (C, a)) -> IM.IntMap (S.Set (C, a))) -> TyState a -> TyState a
+mapClassVars f (TyState u k cvs v cs) = TyState u k (f cvs) v cs
 
-varEnvLens :: Lens' (TyState a) (IM.IntMap (T K))
-varEnvLens f s = fmap (\x -> s { varEnv = x }) (f (varEnv s))
+addVarEnv :: Int -> T K -> TyState a -> TyState a
+addVarEnv i ty (TyState u k cvs v cs) = TyState u k cvs (IM.insert i ty v) cs
 
-constraintsLens :: Lens' (TyState a) (S.Set (a, T K, T K))
-constraintsLens f s = fmap (\x -> s { constraints = x }) (f (constraints s))
+addConstraint :: Ord a => (a, T K, T K) -> TyState a -> TyState a
+addConstraint c (TyState u k cvs v cs) = TyState u k cvs v (S.insert c cs)
 
 type TypeM a = StateT (TyState a) (Either (Error a))
 
@@ -165,7 +155,7 @@
 freshName n k = do
     st <- gets maxU
     Name n (Unique $ st+1) k
-        <$ modifying maxULens (+1)
+        <$ modify (mapMaxU (+1))
 
 higherOrder :: T.Text -> TypeM a (Name K)
 higherOrder t = freshName t (KArr Star Star)
@@ -189,7 +179,7 @@
 -- assumes they have been renamed...
 pushConstraint :: Ord a => a -> T K -> T K -> TypeM a ()
 pushConstraint l ty ty' =
-    modifying constraintsLens (S.insert (l, ty, ty'))
+    modify (addConstraint (l, ty, ty'))
 
 -- TODO: this will need some class context if we permit custom types (Optional)
 checkType :: Ord a => T K -> (C, a) -> TypeM a ()
@@ -217,6 +207,9 @@
 checkType ty@TyArr{} (c, l)                    = throwError $ Doesn'tSatisfy l (void ty) c
 checkType (TyB _ TyVec) (Functor, _)           = pure ()
 checkType (TyB _ TyStream) (Functor, _)        = pure ()
+checkType (TyB _ TyOption) (Functor, _)        = pure ()
+checkType (TyB _ TyStream) (Witherable, _)     = pure ()
+checkType ty (c@Witherable, l)                 = throwError $ Doesn'tSatisfy l (void ty) c
 checkType ty (c@Functor, l)                    = throwError $ Doesn'tSatisfy l (void ty) c
 checkType (TyB _ TyVec) (Foldable, _)          = pure ()
 checkType (TyB _ TyStream) (Foldable, _)       = pure ()
@@ -262,7 +255,7 @@
 tyD0 (FunDecl n@(Name _ (Unique i) _) [] e) = do
     e' <- tyE0 e
     let ty = eLoc e'
-    modifying varEnvLens (IM.insert i ty)
+    modify (addVarEnv i ty)
     pure $ FunDecl (n $> ty) [] e'
 tyD0 FunDecl{} = error "Internal error. Should have been desugared by now."
 
@@ -312,28 +305,28 @@
 tyNumOp :: Ord a => a -> TypeM a (T K)
 tyNumOp l = do
     m <- dummyName "m"
-    modifying classVarsLens (addC m (IsNum, l))
+    modify (mapClassVars (addC m (IsNum, l)))
     let m' = var m
     pure $ tyArr m' (tyArr m' m')
 
 tySemiOp :: Ord a => a -> TypeM a (T K)
 tySemiOp l = do
     m <- dummyName "m"
-    modifying classVarsLens (addC m (IsSemigroup, l))
+    modify (mapClassVars (addC m (IsSemigroup, l)))
     let m' = var m
     pure $ tyArr m' (tyArr m' m')
 
 tyOrd :: Ord a => a -> TypeM a (T K)
 tyOrd l = do
     a <- dummyName "a"
-    modifying classVarsLens (addC a (IsOrd, l))
+    modify (mapClassVars (addC a (IsOrd, l)))
     let a' = var a
     pure $ tyArr a' (tyArr a' tyBool)
 
 tyEq :: Ord a => a -> TypeM a (T K)
 tyEq l = do
     a <- dummyName "a"
-    modifying classVarsLens (addC a (IsEq, l))
+    modify (mapClassVars (addC a (IsEq, l)))
     let a' = var a
     pure $ tyArr a' (tyArr a' tyBool)
 
@@ -341,25 +334,19 @@
 tyM :: Ord a => a -> TypeM a (T K)
 tyM l = do
     a <- dummyName "a"
-    modifying classVarsLens (addC a (IsOrd, l))
+    modify (mapClassVars (addC a (IsOrd, l)))
     let a' = var a
     pure $ tyArr a' (tyArr a' a')
 
 desugar :: a
 desugar = error "Should have been de-sugared in an earlier stage!"
 
-hkt :: T K -> T K -> T K
-hkt = TyApp Star
-
 tyVec :: T K
 tyVec = TyB (KArr Star Star) TyVec
 
 mkVec :: T K -> T K
 mkVec = hkt tyVec
 
-tyOpt :: T K -> T K
-tyOpt = hkt (TyB (KArr Star Star) TyOption)
-
 tyE0 :: Ord a => E a -> TypeM a (E (T K))
 tyE0 (BoolLit _ b)           = pure $ BoolLit tyBool b
 tyE0 (IntLit _ i)            = pure $ IntLit tyI i
@@ -374,6 +361,7 @@
 tyE0 AllColumn{}             = pure $ AllColumn (tyStream tyStr)
 tyE0 (NBuiltin _ Ix)         = pure $ NBuiltin tyI Ix
 tyE0 (NBuiltin _ Fp)         = pure $ NBuiltin tyStr Fp
+tyE0 (NBuiltin _ Nf)         = pure $ NBuiltin tyI Nf
 tyE0 (BBuiltin l Plus)       = BBuiltin <$> tySemiOp l <*> pure Plus
 tyE0 (BBuiltin l Minus)      = BBuiltin <$> tyNumOp l <*> pure Minus
 tyE0 (BBuiltin l Times)      = BBuiltin <$> tyNumOp l <*> pure Times
@@ -410,12 +398,12 @@
 tyE0 (UBuiltin l Parse) = do
     a <- dummyName "a"
     let a' = var a
-    modifying classVarsLens (addC a (IsParseable, l))
+    modify (mapClassVars (addC a (IsParseable, l)))
     pure $ UBuiltin (tyArr tyStr a') Parse
 tyE0 (BBuiltin l Sprintf) = do
     a <- dummyName "a"
     let a' = var a
-    modifying classVarsLens (addC a (IsPrintf, l))
+    modify (mapClassVars (addC a (IsPrintf, l)))
     pure $ BBuiltin (tyArr tyStr (tyArr a' tyStr)) Sprintf
 tyE0 (UBuiltin _ (At i)) = do
     a <- dummyName "a"
@@ -427,8 +415,14 @@
     b <- dummyName "b"
     let a' = var a
         b' = var b
-    modifying classVarsLens (addC a (HasField i b', l))
+    modify (mapClassVars (addC a (HasField i b', l)))
     pure $ UBuiltin (tyArr a' b') (Select i)
+tyE0 (UBuiltin l Dedup) = do
+    a <- dummyName "a"
+    let a' = var a
+        fTy = tyArr (tyStream a') (tyStream a')
+    modify (mapClassVars (addC a (IsEq, l)))
+    pure $ UBuiltin fTy Dedup
 tyE0 (UBuiltin _ Const) = do
     a <- dummyName "a"
     b <- dummyName "b"
@@ -436,11 +430,32 @@
         b' = var b
         fTy = tyArr a' (tyArr b' a')
     pure $ UBuiltin fTy Const
-tyE0 (BBuiltin _ Filter) = do
+tyE0 (UBuiltin l CatMaybes) = do
     a <- dummyName "a"
+    f <- higherOrder "f"
     let a' = var a
-        fTy = tyArr (tyArr a' tyBool) (tyArr (tyStream a') (tyStream a'))
+        f' = var f
+        fTy = tyArr (hkt f' $ tyOpt a') (hkt f' a')
+    modify (mapClassVars (addC f (Witherable, l)))
+    pure $ UBuiltin fTy CatMaybes
+tyE0 (BBuiltin l Filter) = do
+    a <- dummyName "a"
+    f <- higherOrder "f"
+    let a' = var a
+        f' = var f
+        fTy = tyArr (tyArr a' tyBool) (tyArr (hkt f' a') (hkt f' a'))
+    modify (mapClassVars (addC f (Witherable , l)))
     pure $ BBuiltin fTy Filter
+tyE0 (BBuiltin l MapMaybe) = do
+    a <- dummyName "a"
+    b <- dummyName "b"
+    f <- higherOrder "f"
+    let a' = var a
+        b' = var b
+        f' = var f
+        fTy = tyArr (tyArr a' (tyOpt b')) (tyArr (hkt f' a') (hkt f' b'))
+    modify (mapClassVars (addC f (Witherable, l)))
+    pure $ BBuiltin fTy MapMaybe
 tyE0 (BBuiltin l Map) = do
     a <- dummyName "a"
     b <- dummyName "b"
@@ -449,7 +464,7 @@
         b' = var b
         f' = var f
         fTy = tyArr (tyArr a' b') (tyArr (hkt f' a') (hkt f' b'))
-    modifying classVarsLens (addC f (Functor, l))
+    modify (mapClassVars (addC f (Functor, l)))
     pure $ BBuiltin fTy Map
 -- (b -> a -> b) -> b -> Stream a -> b
 tyE0 (TBuiltin l Fold) = do
@@ -460,8 +475,10 @@
         a' = var a
         f' = var f
         fTy = tyArr (tyArr b' (tyArr a' b')) (tyArr b' (tyArr (hkt f' a') b'))
-    modifying classVarsLens (addC f (Foldable, l))
+    modify (mapClassVars (addC f (Foldable, l)))
     pure $ TBuiltin fTy Fold
+tyE0 (TBuiltin _ Captures) =
+    pure $ TBuiltin (tyArr tyStr (tyArr tyI (tyArr tyStr (tyOpt tyStr)))) Captures
 -- (a -> a -> a) -> Stream a -> Stream a
 tyE0 (BBuiltin _ Prior) = do
     a <- dummyName "a"
@@ -516,13 +533,13 @@
 tyE0 (Lam _ n@(Name _ (Unique i) _) e) = do
     a <- dummyName "a"
     let a' = var a
-    modifying varEnvLens (IM.insert i a')
+    modify (addVarEnv i a')
     e' <- tyE0 e
     pure $ Lam (tyArr a' (eLoc e')) (n $> a') e'
 tyE0 (Let _ (n@(Name _ (Unique i) _), eϵ) e) = do
     eϵ' <- tyE0 eϵ
     let bTy = eLoc eϵ'
-    modifying varEnvLens (IM.insert i bTy)
+    modify (addVarEnv i bTy)
     e' <- tyE0 e
     pure $ Let (eLoc e') (n $> bTy, eϵ') e'
 tyE0 (Tup _ es) = do
@@ -552,3 +569,10 @@
     let x = V.head v'
     V.priorM_ (\y y' -> pushConstraint l (eLoc y) (eLoc y')) v'
     pure $ Arr (eLoc x) v'
+tyE0 (Anchor l es) = do
+    es' <- forM es $ \e -> do
+        e' <- tyE0 e
+        a <- dummyName "a"
+        let a' = var a
+        pushConstraint l (tyStream a') (eLoc e') $> e'
+    pure $ Anchor (TyB Star TyUnit) es'
diff --git a/src/Jacinda/Ty/Const.hs b/src/Jacinda/Ty/Const.hs
--- a/src/Jacinda/Ty/Const.hs
+++ b/src/Jacinda/Ty/Const.hs
@@ -3,6 +3,8 @@
                         , tyI
                         , tyF
                         , tyBool
+                        , hkt
+                        , tyOpt
                         ) where
 
 import           Jacinda.AST
@@ -22,3 +24,9 @@
 
 tyStr :: T K
 tyStr = TyB Star TyStr
+
+hkt :: T K -> T K -> T K
+hkt = TyApp Star
+
+tyOpt :: T K -> T K
+tyOpt = hkt (TyB (KArr Star Star) TyOption)
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -38,10 +38,17 @@
         , testCase "running count (lines)" (tyOfT "(+)^0 [:1\"$0" (tyStream tyI))
         , testCase "type of (tally)" (tyOfT "#'hello world'" tyI)
         , testCase "typechecks dfn" (tyFile "test/examples/ab.jac")
-        , testCase "parses parens" (tyFile "lib/example.jac")
+        , testCase "parses parens" (tyFile "examples/lib.jac")
         , testCase "typechecks/parses correctly" (tyFile "test/examples/line.jac")
+        , testCase "split eval" (evalTo "[x+' '+y]|'' split '01-23-1987' /-/" " 01 23 1987")
+        , testCase "captureE" (evalTo "'01-23-1987' ~* 3 /(\\d{2})-(\\d{2})-(\\d{4})/" "Some 1987")
         ]
 
+evalTo :: BSL.ByteString -> String -> Assertion
+evalTo bsl expected =
+    let actual = show (exprEval bsl)
+        in actual @?= expected
+
 pAst :: E ()
 pAst =
     EApp ()
@@ -78,7 +85,7 @@
             (IParseCol () 5)
 
 tyFile :: FilePath -> Assertion
-tyFile = tcIO <=< BSL.readFile
+tyFile = tcIO [] <=< BSL.readFile
 
 tyOfT :: BSL.ByteString -> T K -> Assertion
 tyOfT src expected =
@@ -86,7 +93,7 @@
 
 parseTo :: BSL.ByteString -> E () -> Assertion
 parseTo src e =
-    case rewriteProgram <$> parse src of
+    case rewriteProgram . snd <$> parse src of
         Left err     -> assertFailure (show err)
         Right actual -> void (expr actual) @?= e
 
