diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,55 @@
 # Revision history for zeolite-lang
 
+## 0.17.0.0  -- 2021-04-06
+
+### Language
+
+* **[breaking]** Adds `CharBuffer`, as a random-access, mutable `Char` buffer,
+  and makes `WriteAt` a built-in type. (This is breaking because `CharBuffer`
+  and `WriteAt` are now reserved.)
+
+* **[fix]** Fixes `reduce<String,Container>`, which previously failed.
+
+* **[fix]** Fixes a race condition in function dispatching that was thought to
+  be fixed previously.
+
+* **[behavior]** Improves the memory efficiency of function calls that use 4 or
+  fewer arguments, returns, or function params by reusing memory previously
+  allocated for function calls.
+
+### Libraries
+
+* **[new]** Additions to `lib/util`:
+
+  * **[new]** Adds `Ranges`, for simple min/max determinations.
+
+  * **[new]** Adds `revZeroIndexed` to `Counter`, to create a reversed sequence
+    of zero-indexed container indices.
+
+  * **[new]** Adds `Reversed`, `BySize`, and `AlwaysEqual` comparators, to
+    modify `LessThan` and `Equals` behaviors for objects.
+
+  * **[new]** Adds `OrderH` and `ReadAtH`, to extend `LessThan` and `Equals`
+    comparisons to simple containers.
+
+* **[new]** Additions to `lib/container`:
+
+  * **[new]** Adds `Sorting`, to support sorting of random-access containers.
+
+  * **[new]** Adds `TreeSet`, to manage set membership of any sortable type.
+
+  * **[new]** Adds `AutoBinaryTree`, to provide binary search tree (BST)
+    functionality to custom containers that need to augment each node with
+    additional data.
+
+  * **[new]** Implements `Container.size()` in `SearchTree`.
+
+  * **[new]** Adds `KeyValueH`, to extend `LessThan` and `Equals` comparisons to
+    `KeyValue`.
+
+* **[new]** Adds `CategoricalTree` to `lib/math`, as a dynamic representation
+  of a [categorical distribution][categorical] over objects of any sortable type.
+
 ## 0.16.1.0  -- 2021-04-02
 
 ### Language
@@ -764,6 +814,7 @@
 
 * First version. Released on an unsuspecting world.
 
+[categorical]: https://en.wikipedia.org/wiki/Categorical_distribution
 [megaparsec]: https://hackage.haskell.org/package/megaparsec
 [microlens]: https://hackage.haskell.org/package/microlens
 [microlens-th]: https://hackage.haskell.org/package/microlens-th
diff --git a/base/.zeolite-module b/base/.zeolite-module
--- a/base/.zeolite-module
+++ b/base/.zeolite-module
@@ -2,36 +2,40 @@
 path: "base"
 extra_files: [
   category_source {
-    source: "base/Category_Bool.cpp"
+    source: "base/src/Extension_Bool.cpp"
     categories: [Bool]
   }
   category_source {
-    source: "base/Category_Char.cpp"
+    source: "base/src/Extension_Char.cpp"
     categories: [Char]
   }
   category_source {
-    source: "base/Category_Float.cpp"
+    source: "base/src/Extension_CharBuffer.cpp"
+    categories: [CharBuffer]
+  }
+  category_source {
+    source: "base/src/Extension_Float.cpp"
     categories: [Float]
   }
   category_source {
-    source: "base/Category_Int.cpp"
+    source: "base/src/Extension_Int.cpp"
     categories: [Int]
   }
   category_source {
-    source: "base/Category_String.cpp"
+    source: "base/src/Extension_String.cpp"
     categories: [String]
   }
   "base/category-header.hpp"
-  "base/category-source.cpp"
   "base/category-source.hpp"
   "base/cycle-check.hpp"
   "base/function.hpp"
-  "base/logging.cpp"
   "base/logging.hpp"
-  "base/types.cpp"
-  "base/types.hpp"
   "base/thread-capture.h"
   "base/thread-crosser.h"
-  "base/thread-crosser.cc"
+  "base/types.hpp"
+  "base/src/category-source.cpp"
+  "base/src/logging.cpp"
+  "base/src/thread-crosser.cc"
+  "base/src/types.cpp"
 ]
 mode: incremental {}
diff --git a/base/Category_Bool.cpp b/base/Category_Bool.cpp
deleted file mode 100644
--- a/base/Category_Bool.cpp
+++ /dev/null
@@ -1,231 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-#include "Category_Bool.hpp"
-
-#include <map>
-#include <sstream>
-
-#include "category-source.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_AsInt.hpp"
-#include "Category_AsFloat.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Default.hpp"
-#include "Category_Equals.hpp"
-
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-namespace {
-const int collection_Bool = 0;
-}  // namespace
-const void* const Functions_Bool = &collection_Bool;
-namespace {
-class Category_Bool;
-class Type_Bool;
-class Value_Bool;
-struct Category_Bool : public TypeCategory {
-  std::string CategoryName() const final { return "Bool"; }
-  Category_Bool() {
-    CycleCheck<Category_Bool>::Check();
-    CycleCheck<Category_Bool> marker(*this);
-    TRACE_FUNCTION("Bool (init @category)")
-  }
-  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Category_Bool::*)(const ParamTuple&, const ValueTuple&);
-    return TypeCategory::Dispatch(label, params, args);
-  }
-};
-Category_Bool& CreateCategory_Bool() {
-  static auto& category = *new Category_Bool();
-  return category;
-}
-struct Type_Bool : public TypeInstance {
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  void BuildTypeName(std::ostream& output) const final {
-    return TypeInstance::TypeNameFrom(output, parent);
-  }
-  Category_Bool& parent;
-  bool CanConvertFrom(const S<const TypeInstance>& from) const final {
-    std::vector<S<const TypeInstance>> args;
-    if (!from->TypeArgsForParent(parent, args)) return false;
-    if(args.size() != 0) {
-      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
-    }
-    return true;
-  }
-  void Params_Bool(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsBool(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsInt(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsFloat(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_Formatted(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  bool TypeArgsForParent(const TypeCategory& category, std::vector<S<const TypeInstance>>& args) const final {
-    using CallType = void(Type_Bool::*)(std::vector<S<const TypeInstance>>&)const;
-    static DispatchSingle<CallType> all_calls[] = {
-      DispatchSingle<CallType>(&GetCategory_AsBool(),    &Type_Bool::Params_AsBool),
-      DispatchSingle<CallType>(&GetCategory_AsFloat(),   &Type_Bool::Params_AsFloat),
-      DispatchSingle<CallType>(&GetCategory_AsInt(),     &Type_Bool::Params_AsInt),
-      DispatchSingle<CallType>(&GetCategory_Bool(),      &Type_Bool::Params_Bool),
-      DispatchSingle<CallType>(&GetCategory_Formatted(), &Type_Bool::Params_Formatted),
-      DispatchSingle<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls);
-    if (call) {
-      (this->*call->value)(args);
-      return true;
-    }
-    return false;
-  }
-  Type_Bool(Category_Bool& p, Params<0>::Type params) : parent(p) {
-    CycleCheck<Type_Bool>::Check();
-    CycleCheck<Type_Bool> marker(*this);
-    TRACE_FUNCTION("Bool (init @type)")
-  }
-  ReturnTuple Dispatch(const S<TypeInstance>& self, const TypeFunction& label,
-                       const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Type_Bool::*)(const ParamTuple&, const ValueTuple&);
-    static const CallType Table_Default[] = {
-      &Type_Bool::Call_default,
-    };
-    static const CallType Table_Equals[] = {
-      &Type_Bool::Call_equals,
-    };
-    static DispatchTable<CallType> all_tables[] = {
-      DispatchTable<CallType>(Functions_Default, Table_Default),
-      DispatchTable<CallType>(Functions_Equals,  Table_Equals),
-      DispatchTable<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);
-    if (table) {
-      if (label.function_num < 0 || label.function_num >= table->size) {
-        FAIL() << "Bad function call " << label;
-      } else {
-        return (this->*table->table[label.function_num])(params, args);
-      }
-    }
-    return TypeInstance::Dispatch(self, label, params, args);
-  }
-  ReturnTuple Call_default(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
-};
-S<Type_Bool> CreateType_Bool(Params<0>::Type params) {
-  static const auto cached = S_get(new Type_Bool(CreateCategory_Bool(), Params<0>::Type()));
-  return cached;
-}
-struct Value_Bool : public TypeValue {
-  Value_Bool(S<Type_Bool> p, bool value) : parent(p), value_(value) {}
-  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
-    using CallType = ReturnTuple(Value_Bool::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
-    static const CallType Table_AsBool[] = {
-      &Value_Bool::Call_asBool,
-    };
-    static const CallType Table_AsFloat[] = {
-      &Value_Bool::Call_asFloat,
-    };
-    static const CallType Table_AsInt[] = {
-      &Value_Bool::Call_asInt,
-    };
-    static const CallType Table_Formatted[] = {
-      &Value_Bool::Call_formatted,
-    };
-    static DispatchTable<CallType> all_tables[] = {
-      DispatchTable<CallType>(Functions_AsBool,    Table_AsBool),
-      DispatchTable<CallType>(Functions_AsFloat,   Table_AsFloat),
-      DispatchTable<CallType>(Functions_AsInt,     Table_AsInt),
-      DispatchTable<CallType>(Functions_Formatted, Table_Formatted),
-      DispatchTable<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);
-    if (table) {
-      if (label.function_num < 0 || label.function_num >= table->size) {
-        FAIL() << "Bad function call " << label;
-      } else {
-        return (this->*table->table[label.function_num])(self, params, args);
-      }
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-  std::string CategoryName() const final { return parent->CategoryName(); }
-  bool AsBool() const final { return value_; }
-  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  const S<Type_Bool> parent;
-  const bool value_;
-};
-
-ReturnTuple Type_Bool::Call_default(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.default")
-  return ReturnTuple(Box_Bool(false));
-}
-ReturnTuple Type_Bool::Call_equals(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.equals")
-  const bool Var_arg1 = (args.At(0))->AsBool();
-  const bool Var_arg2 = (args.At(1))->AsBool();
-  return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-}
-ReturnTuple Value_Bool::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.asBool")
-  return ReturnTuple(Var_self);
-}
-ReturnTuple Value_Bool::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.asFloat")
-  return ReturnTuple(Box_Float(value_ ? 1.0 : 0.0));
-}
-ReturnTuple Value_Bool::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.asInt")
-  return ReturnTuple(Box_Int(value_? 1 : 0));
-}
-ReturnTuple Value_Bool::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Bool.formatted")
-  return ReturnTuple(Box_String(value_? "true" : "false"));
-}
-const S<TypeValue>& Var_true = *new S<TypeValue>(new Value_Bool(CreateType_Bool(Params<0>::Type()), true));
-const S<TypeValue>& Var_false = *new S<TypeValue>(new Value_Bool(CreateType_Bool(Params<0>::Type()), false));
-}  // namespace
-TypeCategory& GetCategory_Bool() {
-  return CreateCategory_Bool();
-}
-S<TypeInstance> GetType_Bool(Params<0>::Type params) {
-  return CreateType_Bool(params);
-}
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-
-S<TypeValue> Box_Bool(bool value) {
-  return value? Var_true : Var_false;
-}
diff --git a/base/Category_Char.cpp b/base/Category_Char.cpp
deleted file mode 100644
--- a/base/Category_Char.cpp
+++ /dev/null
@@ -1,257 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-#include "Category_Char.hpp"
-
-#include <map>
-#include <sstream>
-
-#include "category-source.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_AsChar.hpp"
-#include "Category_AsInt.hpp"
-#include "Category_AsFloat.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Default.hpp"
-#include "Category_Equals.hpp"
-#include "Category_LessThan.hpp"
-
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-namespace {
-const int collection_Char = 0;
-}  // namespace
-const void* const Functions_Char = &collection_Char;
-namespace {
-class Category_Char;
-class Type_Char;
-class Value_Char;
-struct Category_Char : public TypeCategory {
-  std::string CategoryName() const final { return "Char"; }
-  Category_Char() {
-    CycleCheck<Category_Char>::Check();
-    CycleCheck<Category_Char> marker(*this);
-    TRACE_FUNCTION("Char (init @category)")
-  }
-  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Category_Char::*)(const ParamTuple&, const ValueTuple&);
-    return TypeCategory::Dispatch(label, params, args);
-  }
-};
-Category_Char& CreateCategory_Char() {
-  static auto& category = *new Category_Char();
-  return category;
-}
-struct Type_Char : public TypeInstance {
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  void BuildTypeName(std::ostream& output) const final {
-    return TypeInstance::TypeNameFrom(output, parent);
-  }
-  Category_Char& parent;
-  bool CanConvertFrom(const S<const TypeInstance>& from) const final {
-    std::vector<S<const TypeInstance>> args;
-    if (!from->TypeArgsForParent(parent, args)) return false;
-    if(args.size() != 0) {
-      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
-    }
-    return true;
-  }
-  void Params_Char(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsBool(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsChar(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsInt(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsFloat(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_Formatted(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  bool TypeArgsForParent(const TypeCategory& category, std::vector<S<const TypeInstance>>& args) const final {
-    using CallType = void(Type_Char::*)(std::vector<S<const TypeInstance>>&)const;
-    static DispatchSingle<CallType> all_calls[] = {
-      DispatchSingle<CallType>(&GetCategory_AsBool(),    &Type_Char::Params_AsBool),
-      DispatchSingle<CallType>(&GetCategory_AsChar(),    &Type_Char::Params_AsChar),
-      DispatchSingle<CallType>(&GetCategory_AsFloat(),   &Type_Char::Params_AsFloat),
-      DispatchSingle<CallType>(&GetCategory_AsInt(),     &Type_Char::Params_AsInt),
-      DispatchSingle<CallType>(&GetCategory_Char(),      &Type_Char::Params_Char),
-      DispatchSingle<CallType>(&GetCategory_Formatted(), &Type_Char::Params_Formatted),
-      DispatchSingle<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls);
-    if (call) {
-      (this->*call->value)(args);
-      return true;
-    }
-    return false;
-  }
-  Type_Char(Category_Char& p, Params<0>::Type params) : parent(p) {
-    CycleCheck<Type_Char>::Check();
-    CycleCheck<Type_Char> marker(*this);
-    TRACE_FUNCTION("Char (init @type)")
-  }
-  ReturnTuple Dispatch(const S<TypeInstance>& self, const TypeFunction& label,
-                       const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Type_Char::*)(const ParamTuple&, const ValueTuple&);
-    static const CallType Table_Default[] = {
-      &Type_Char::Call_default,
-    };
-    static const CallType Table_Equals[] = {
-      &Type_Char::Call_equals,
-    };
-    static const CallType Table_LessThan[] = {
-      &Type_Char::Call_lessThan,
-    };
-    static DispatchTable<CallType> all_tables[] = {
-      DispatchTable<CallType>(Functions_Default,  Table_Default),
-      DispatchTable<CallType>(Functions_Equals,   Table_Equals),
-      DispatchTable<CallType>(Functions_LessThan, Table_LessThan),
-      DispatchTable<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);
-    if (table) {
-      if (label.function_num < 0 || label.function_num >= table->size) {
-        FAIL() << "Bad function call " << label;
-      } else {
-        return (this->*table->table[label.function_num])(params, args);
-      }
-    }
-    return TypeInstance::Dispatch(self, label, params, args);
-  }
-  ReturnTuple Call_default(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
-};
-S<Type_Char> CreateType_Char(Params<0>::Type params) {
-  static const auto cached = S_get(new Type_Char(CreateCategory_Char(), Params<0>::Type()));
-  return cached;
-}
-struct Value_Char : public TypeValue {
-  Value_Char(S<Type_Char> p, PrimChar value) : parent(p), value_(value) {}
-  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
-    using CallType = ReturnTuple(Value_Char::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
-    static const CallType Table_AsBool[] = {
-      &Value_Char::Call_asBool,
-    };
-    static const CallType Table_AsChar[] = {
-      &Value_Char::Call_asChar,
-    };
-    static const CallType Table_AsFloat[] = {
-      &Value_Char::Call_asFloat,
-    };
-    static const CallType Table_AsInt[] = {
-      &Value_Char::Call_asInt,
-    };
-    static const CallType Table_Formatted[] = {
-      &Value_Char::Call_formatted,
-    };
-    static DispatchTable<CallType> all_tables[] = {
-      DispatchTable<CallType>(Functions_AsBool,    Table_AsBool),
-      DispatchTable<CallType>(Functions_AsChar,    Table_AsChar),
-      DispatchTable<CallType>(Functions_AsFloat,   Table_AsFloat),
-      DispatchTable<CallType>(Functions_AsInt,     Table_AsInt),
-      DispatchTable<CallType>(Functions_Formatted, Table_Formatted),
-      DispatchTable<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);
-    if (table) {
-      if (label.function_num < 0 || label.function_num >= table->size) {
-        FAIL() << "Bad function call " << label;
-      } else {
-        return (this->*table->table[label.function_num])(self, params, args);
-      }
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-  std::string CategoryName() const final { return parent->CategoryName(); }
-  PrimChar AsChar() const final { return value_; }
-  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  const S<Type_Char> parent;
-  const PrimChar value_;
-};
-
-ReturnTuple Type_Char::Call_default(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.default")
-  return ReturnTuple(Box_Char('\0'));
-}
-ReturnTuple Type_Char::Call_equals(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.equals")
-  const PrimChar Var_arg1 = (args.At(0))->AsChar();
-  const PrimChar Var_arg2 = (args.At(1))->AsChar();
-      return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-}
-ReturnTuple Type_Char::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.lessThan")
-  const PrimChar Var_arg1 = (args.At(0))->AsChar();
-  const PrimChar Var_arg2 = (args.At(1))->AsChar();
-  return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
-}
-ReturnTuple Value_Char::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.asBool")
-  return ReturnTuple(Box_Bool(value_ != '\0'));
-}
-ReturnTuple Value_Char::Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.asChar")
-  return ReturnTuple(Var_self);
-}
-ReturnTuple Value_Char::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.asFloat")
-  return ReturnTuple(Box_Float(value_));
-}
-ReturnTuple Value_Char::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.asInt")
-  return ReturnTuple(Box_Int(value_));
-}
-ReturnTuple Value_Char::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Char.formatted")
-  std::ostringstream output;
-  output << value_;
-  return ReturnTuple(Box_String(output.str()));
-}
-}  // namespace
-TypeCategory& GetCategory_Char() {
-  return CreateCategory_Char();
-}
-S<TypeInstance> GetType_Char(Params<0>::Type params) {
-  return CreateType_Char(params);
-}
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-
-S<TypeValue> Box_Char(PrimChar value) {
-  return S_get(new Value_Char(CreateType_Char(Params<0>::Type()), value));
-}
diff --git a/base/Category_Float.cpp b/base/Category_Float.cpp
deleted file mode 100644
--- a/base/Category_Float.cpp
+++ /dev/null
@@ -1,243 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-#include "Category_Float.hpp"
-
-#include <map>
-#include <sstream>
-
-#include "category-source.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_AsInt.hpp"
-#include "Category_AsFloat.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Default.hpp"
-#include "Category_Equals.hpp"
-#include "Category_LessThan.hpp"
-
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-namespace {
-const int collection_Float = 0;
-}  // namespace
-const void* const Functions_Float = &collection_Float;
-namespace {
-class Category_Float;
-class Type_Float;
-class Value_Float;
-struct Category_Float : public TypeCategory {
-  std::string CategoryName() const final { return "Float"; }
-  Category_Float() {
-    CycleCheck<Category_Float>::Check();
-    CycleCheck<Category_Float> marker(*this);
-    TRACE_FUNCTION("Float (init @category)")
-  }
-  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Category_Float::*)(const ParamTuple&, const ValueTuple&);
-    return TypeCategory::Dispatch(label, params, args);
-  }
-};
-Category_Float& CreateCategory_Float() {
-  static auto& category = *new Category_Float();
-  return category;
-}
-struct Type_Float : public TypeInstance {
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  void BuildTypeName(std::ostream& output) const final {
-    return TypeInstance::TypeNameFrom(output, parent);
-  }
-  Category_Float& parent;
-  bool CanConvertFrom(const S<const TypeInstance>& from) const final {
-    std::vector<S<const TypeInstance>> args;
-    if (!from->TypeArgsForParent(parent, args)) return false;
-    if(args.size() != 0) {
-      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
-    }
-    return true;
-  }
-  void Params_Float(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsBool(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsInt(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsFloat(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_Formatted(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  bool TypeArgsForParent(const TypeCategory& category, std::vector<S<const TypeInstance>>& args) const final {
-    using CallType = void(Type_Float::*)(std::vector<S<const TypeInstance>>&)const;
-    static DispatchSingle<CallType> all_calls[] = {
-      DispatchSingle<CallType>(&GetCategory_AsBool(),    &Type_Float::Params_AsBool),
-      DispatchSingle<CallType>(&GetCategory_AsFloat(),   &Type_Float::Params_AsFloat),
-      DispatchSingle<CallType>(&GetCategory_AsInt(),     &Type_Float::Params_AsInt),
-      DispatchSingle<CallType>(&GetCategory_Float(),     &Type_Float::Params_Float),
-      DispatchSingle<CallType>(&GetCategory_Formatted(), &Type_Float::Params_Formatted),
-      DispatchSingle<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls);
-    if (call) {
-      (this->*call->value)(args);
-      return true;
-    }
-    return false;
-  }
-  Type_Float(Category_Float& p, Params<0>::Type params) : parent(p) {
-    CycleCheck<Type_Float>::Check();
-    CycleCheck<Type_Float> marker(*this);
-    TRACE_FUNCTION("Float (init @type)")
-  }
-  ReturnTuple Dispatch(const S<TypeInstance>& self, const TypeFunction& label,
-                       const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Type_Float::*)(const ParamTuple&, const ValueTuple&);
-    static const CallType Table_Default[] = {
-      &Type_Float::Call_default,
-    };
-    static const CallType Table_Equals[] = {
-      &Type_Float::Call_equals,
-    };
-    static const CallType Table_LessThan[] = {
-      &Type_Float::Call_lessThan,
-    };
-    static DispatchTable<CallType> all_tables[] = {
-      DispatchTable<CallType>(Functions_Default,  Table_Default),
-      DispatchTable<CallType>(Functions_Equals,   Table_Equals),
-      DispatchTable<CallType>(Functions_LessThan, Table_LessThan),
-      DispatchTable<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);
-    if (table) {
-      if (label.function_num < 0 || label.function_num >= table->size) {
-        FAIL() << "Bad function call " << label;
-      } else {
-        return (this->*table->table[label.function_num])(params, args);
-      }
-    }
-    return TypeInstance::Dispatch(self, label, params, args);
-  }
-  ReturnTuple Call_default(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
-};
-S<Type_Float> CreateType_Float(Params<0>::Type params) {
-  static const auto cached = S_get(new Type_Float(CreateCategory_Float(), Params<0>::Type()));
-  return cached;
-}
-struct Value_Float : public TypeValue {
-  Value_Float(S<Type_Float> p, PrimFloat value) : parent(p), value_(value) {}
-  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
-    using CallType = ReturnTuple(Value_Float::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
-    static const CallType Table_AsBool[] = {
-      &Value_Float::Call_asBool,
-    };
-    static const CallType Table_AsFloat[] = {
-      &Value_Float::Call_asFloat,
-    };
-    static const CallType Table_AsInt[] = {
-      &Value_Float::Call_asInt,
-    };
-    static const CallType Table_Formatted[] = {
-      &Value_Float::Call_formatted,
-    };
-    static DispatchTable<CallType> all_tables[] = {
-      DispatchTable<CallType>(Functions_AsBool,    Table_AsBool),
-      DispatchTable<CallType>(Functions_AsFloat,   Table_AsFloat),
-      DispatchTable<CallType>(Functions_AsInt,     Table_AsInt),
-      DispatchTable<CallType>(Functions_Formatted, Table_Formatted),
-      DispatchTable<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);
-    if (table) {
-      if (label.function_num < 0 || label.function_num >= table->size) {
-        FAIL() << "Bad function call " << label;
-      } else {
-        return (this->*table->table[label.function_num])(self, params, args);
-      }
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-  std::string CategoryName() const final { return parent->CategoryName(); }
-  PrimFloat AsFloat() const final { return value_; }
-  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  const S<Type_Float> parent;
-  const PrimFloat value_;
-};
-
-ReturnTuple Type_Float::Call_default(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.default")
-  return ReturnTuple(Box_Float(0.0));
-}
-ReturnTuple Type_Float::Call_equals(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.equals")
-  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
-  const PrimFloat Var_arg2 = (args.At(1))->AsFloat();
-  return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-}
-ReturnTuple Type_Float::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.lessThan")
-  const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
-  const PrimFloat Var_arg2 = (args.At(1))->AsFloat();
-  return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
-}
-ReturnTuple Value_Float::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.asBool")
-  return ReturnTuple(Box_Bool(value_ != 0.0));
-}
-ReturnTuple Value_Float::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.asFloat")
-  return ReturnTuple(Var_self);
-}
-ReturnTuple Value_Float::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.asInt")
-  return ReturnTuple(Box_Int(value_));
-}
-ReturnTuple Value_Float::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Float.formatted")
-  std::ostringstream output;
-  output << value_;
-  return ReturnTuple(Box_String(output.str()));
-}
-}  // namespace
-TypeCategory& GetCategory_Float() {
-  return CreateCategory_Float();
-}
-S<TypeInstance> GetType_Float(Params<0>::Type params) {
-  return CreateType_Float(params);
-}
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-
-S<TypeValue> Box_Float(PrimFloat value) {
-  return S_get(new Value_Float(CreateType_Float(Params<0>::Type()), value));
-}
diff --git a/base/Category_Int.cpp b/base/Category_Int.cpp
deleted file mode 100644
--- a/base/Category_Int.cpp
+++ /dev/null
@@ -1,257 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-#include "Category_Int.hpp"
-
-#include <map>
-#include <sstream>
-
-#include "category-source.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_AsChar.hpp"
-#include "Category_AsInt.hpp"
-#include "Category_AsFloat.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Default.hpp"
-#include "Category_Equals.hpp"
-#include "Category_LessThan.hpp"
-
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-namespace {
-const int collection_Int = 0;
-}  // namespace
-const void* const Functions_Int = &collection_Int;
-namespace {
-class Category_Int;
-class Type_Int;
-class Value_Int;
-struct Category_Int : public TypeCategory {
-  std::string CategoryName() const final { return "Int"; }
-  Category_Int() {
-    CycleCheck<Category_Int>::Check();
-    CycleCheck<Category_Int> marker(*this);
-    TRACE_FUNCTION("Int (init @category)")
-  }
-  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Category_Int::*)(const ParamTuple&, const ValueTuple&);
-    return TypeCategory::Dispatch(label, params, args);
-  }
-};
-Category_Int& CreateCategory_Int() {
-  static auto& category = *new Category_Int();
-  return category;
-}
-struct Type_Int : public TypeInstance {
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  void BuildTypeName(std::ostream& output) const final {
-    return TypeInstance::TypeNameFrom(output, parent);
-  }
-  Category_Int& parent;
-  bool CanConvertFrom(const S<const TypeInstance>& from) const final {
-    std::vector<S<const TypeInstance>> args;
-    if (!from->TypeArgsForParent(parent, args)) return false;
-    if(args.size() != 0) {
-      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
-    }
-    return true;
-  }
-  void Params_Int(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsBool(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsChar(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsInt(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsFloat(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_Formatted(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  bool TypeArgsForParent(const TypeCategory& category, std::vector<S<const TypeInstance>>& args) const final {
-    using CallType = void(Type_Int::*)(std::vector<S<const TypeInstance>>&)const;
-    static DispatchSingle<CallType> all_calls[] = {
-      DispatchSingle<CallType>(&GetCategory_AsBool(),    &Type_Int::Params_AsBool),
-      DispatchSingle<CallType>(&GetCategory_AsChar(),    &Type_Int::Params_AsChar),
-      DispatchSingle<CallType>(&GetCategory_AsFloat(),   &Type_Int::Params_AsFloat),
-      DispatchSingle<CallType>(&GetCategory_AsInt(),     &Type_Int::Params_AsInt),
-      DispatchSingle<CallType>(&GetCategory_Formatted(), &Type_Int::Params_Formatted),
-      DispatchSingle<CallType>(&GetCategory_Int(),       &Type_Int::Params_Int),
-      DispatchSingle<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls);
-    if (call) {
-      (this->*call->value)(args);
-      return true;
-    }
-    return false;
-  }
-  Type_Int(Category_Int& p, Params<0>::Type params) : parent(p) {
-    CycleCheck<Type_Int>::Check();
-    CycleCheck<Type_Int> marker(*this);
-    TRACE_FUNCTION("Int (init @type)")
-  }
-  ReturnTuple Dispatch(const S<TypeInstance>& self, const TypeFunction& label,
-                       const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Type_Int::*)(const ParamTuple&, const ValueTuple&);
-    static const CallType Table_Default[] = {
-      &Type_Int::Call_default,
-    };
-    static const CallType Table_Equals[] = {
-      &Type_Int::Call_equals,
-    };
-    static const CallType Table_LessThan[] = {
-      &Type_Int::Call_lessThan,
-    };
-    static DispatchTable<CallType> all_tables[] = {
-      DispatchTable<CallType>(Functions_Default,  Table_Default),
-      DispatchTable<CallType>(Functions_Equals,   Table_Equals),
-      DispatchTable<CallType>(Functions_LessThan, Table_LessThan),
-      DispatchTable<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);
-    if (table) {
-      if (label.function_num < 0 || label.function_num >= table->size) {
-        FAIL() << "Bad function call " << label;
-      } else {
-        return (this->*table->table[label.function_num])(params, args);
-      }
-    }
-    return TypeInstance::Dispatch(self, label, params, args);
-  }
-  ReturnTuple Call_default(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
-};
-S<Type_Int> CreateType_Int(Params<0>::Type params) {
-  static const auto cached = S_get(new Type_Int(CreateCategory_Int(), Params<0>::Type()));
-  return cached;
-}
-struct Value_Int : public TypeValue {
-  Value_Int(S<Type_Int> p, PrimInt value) : parent(p), value_(value) {}
-  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
-    using CallType = ReturnTuple(Value_Int::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
-    static const CallType Table_AsBool[] = {
-      &Value_Int::Call_asBool,
-    };
-    static const CallType Table_AsChar[] = {
-      &Value_Int::Call_asChar,
-    };
-    static const CallType Table_AsFloat[] = {
-      &Value_Int::Call_asFloat,
-    };
-    static const CallType Table_AsInt[] = {
-      &Value_Int::Call_asInt,
-    };
-    static const CallType Table_Formatted[] = {
-      &Value_Int::Call_formatted,
-    };
-    static DispatchTable<CallType> all_tables[] = {
-      DispatchTable<CallType>(Functions_AsBool,    Table_AsBool),
-      DispatchTable<CallType>(Functions_AsChar,    Table_AsChar),
-      DispatchTable<CallType>(Functions_AsFloat,   Table_AsFloat),
-      DispatchTable<CallType>(Functions_AsInt,     Table_AsInt),
-      DispatchTable<CallType>(Functions_Formatted, Table_Formatted),
-      DispatchTable<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);
-    if (table) {
-      if (label.function_num < 0 || label.function_num >= table->size) {
-        FAIL() << "Bad function call " << label;
-      } else {
-        return (this->*table->table[label.function_num])(self, params, args);
-      }
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-  std::string CategoryName() const final { return parent->CategoryName(); }
-  PrimInt AsInt() const final { return value_; }
-  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  const S<Type_Int> parent;
-  const PrimInt value_;
-};
-
-ReturnTuple Type_Int::Call_default(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.default")
-  return ReturnTuple(Box_Int(0));
-}
-ReturnTuple Type_Int::Call_equals(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.equals")
-  const PrimInt Var_arg1 = (args.At(0))->AsInt();
-  const PrimInt Var_arg2 = (args.At(1))->AsInt();
-  return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
-}
-ReturnTuple Type_Int::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.lessThan")
-  const PrimInt Var_arg1 = (args.At(0))->AsInt();
-  const PrimInt Var_arg2 = (args.At(1))->AsInt();
-  return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
-}
-ReturnTuple Value_Int::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.asBool")
-  return ReturnTuple(Box_Bool(value_ != 0));
-}
-ReturnTuple Value_Int::Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.asChar")
-  return ReturnTuple(Box_Char(value_ % 0xff));
-}
-ReturnTuple Value_Int::Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.asFloat")
-  return ReturnTuple(Box_Float(value_));
-}
-ReturnTuple Value_Int::Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.asInt")
-  return ReturnTuple(Var_self);
-}
-ReturnTuple Value_Int::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("Int.formatted")
-  std::ostringstream output;
-  output << value_;
-  return ReturnTuple(Box_String(output.str()));
-}
-}  // namespace
-TypeCategory& GetCategory_Int() {
-  return CreateCategory_Int();
-}
-S<TypeInstance> GetType_Int(Params<0>::Type params) {
-  return CreateType_Int(params);
-}
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-
-S<TypeValue> Box_Int(PrimInt value) {
-  return S_get(new Value_Int(CreateType_Int(Params<0>::Type()), value));
-}
diff --git a/base/Category_String.cpp b/base/Category_String.cpp
deleted file mode 100644
--- a/base/Category_String.cpp
+++ /dev/null
@@ -1,354 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-#include "Category_String.hpp"
-
-#include <map>
-#include <sstream>
-
-#include "category-source.hpp"
-#include "Category_AsBool.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_Char.hpp"
-#include "Category_Container.hpp"
-#include "Category_ReadAt.hpp"
-#include "Category_SubSequence.hpp"
-#include "Category_Append.hpp"
-#include "Category_Build.hpp"
-#include "Category_Default.hpp"
-#include "Category_DefaultOrder.hpp"
-#include "Category_Order.hpp"
-#include "Category_Equals.hpp"
-#include "Category_LessThan.hpp"
-
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-namespace {
-const int collection_String = 0;
-}  // namespace
-const void* const Functions_String = &collection_String;
-const ValueFunction& Function_String_subSequence = (*new ValueFunction{ 0, 2, 1, "String", "subSequence", Functions_String, 0 });
-const TypeFunction& Function_String_builder = (*new TypeFunction{ 0, 0, 1, "String", "builder", Functions_String, 0 });
-namespace {
-class Category_String;
-class Type_String;
-class Value_String;
-struct Category_String : public TypeCategory {
-  std::string CategoryName() const final { return "String"; }
-  Category_String() {
-    CycleCheck<Category_String>::Check();
-    CycleCheck<Category_String> marker(*this);
-    TRACE_FUNCTION("String (init @category)")
-  }
-  ReturnTuple Dispatch(const CategoryFunction& label, const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Category_String::*)(const ParamTuple&, const ValueTuple&);
-    return TypeCategory::Dispatch(label, params, args);
-  }
-};
-Category_String& CreateCategory_String() {
-  static auto& category = *new Category_String();
-  return category;
-}
-struct Type_String : public TypeInstance {
-  std::string CategoryName() const final { return parent.CategoryName(); }
-  void BuildTypeName(std::ostream& output) const final {
-    return TypeInstance::TypeNameFrom(output, parent);
-  }
-  Category_String& parent;
-  bool CanConvertFrom(const S<const TypeInstance>& from) const final {
-    std::vector<S<const TypeInstance>> args;
-    if (!from->TypeArgsForParent(parent, args)) return false;
-    if(args.size() != 0) {
-      FAIL() << "Wrong number of args (" << args.size() << ")  for " << CategoryName();
-    }
-    return true;
-  }
-  void Params_String(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_AsBool(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_Formatted(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_ReadAt(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{GetType_Char(T_get())};
-  }
-  void Params_SubSequence(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{};
-  }
-  void Params_DefaultOrder(std::vector<S<const TypeInstance>>& args) const {
-    args = std::vector<S<const TypeInstance>>{GetType_Char(T_get())};
-  }
-  bool TypeArgsForParent(const TypeCategory& category, std::vector<S<const TypeInstance>>& args) const final {
-    using CallType = void(Type_String::*)(std::vector<S<const TypeInstance>>&)const;
-    static DispatchSingle<CallType> all_calls[] = {
-      DispatchSingle<CallType>(&GetCategory_String(),       &Type_String::Params_String),
-      DispatchSingle<CallType>(&GetCategory_AsBool(),       &Type_String::Params_AsBool),
-      DispatchSingle<CallType>(&GetCategory_Formatted(),    &Type_String::Params_Formatted),
-      DispatchSingle<CallType>(&GetCategory_ReadAt(),       &Type_String::Params_ReadAt),
-      DispatchSingle<CallType>(&GetCategory_SubSequence(),  &Type_String::Params_SubSequence),
-      DispatchSingle<CallType>(&GetCategory_DefaultOrder(), &Type_String::Params_DefaultOrder),
-      DispatchSingle<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls);
-    if (call) {
-      (this->*call->value)(args);
-      return true;
-    }
-    return false;
-  }
-  Type_String(Category_String& p, Params<0>::Type params) : parent(p) {
-    CycleCheck<Type_String>::Check();
-    CycleCheck<Type_String> marker(*this);
-    TRACE_FUNCTION("String (init @type)")
-  }
-  ReturnTuple Dispatch(const S<TypeInstance>& self, const TypeFunction& label,
-                       const ParamTuple& params, const ValueTuple& args) final {
-    using CallType = ReturnTuple(Type_String::*)(const ParamTuple&, const ValueTuple&);
-    static const CallType Table_Default[] = {
-      &Type_String::Call_default,
-    };
-    static const CallType Table_Equals[] = {
-      &Type_String::Call_equals,
-    };
-    static const CallType Table_LessThan[] = {
-      &Type_String::Call_lessThan,
-    };
-    static const CallType Table_String[] = {
-      &Type_String::Call_builder,
-    };
-    static DispatchTable<CallType> all_tables[] = {
-      DispatchTable<CallType>(Functions_Default,  Table_Default),
-      DispatchTable<CallType>(Functions_Equals,   Table_Equals),
-      DispatchTable<CallType>(Functions_LessThan, Table_LessThan),
-      DispatchTable<CallType>(Functions_String,   Table_String),
-      DispatchTable<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);
-    if (table) {
-      if (label.function_num < 0 || label.function_num >= table->size) {
-        FAIL() << "Bad function call " << label;
-      } else {
-        return (this->*table->table[label.function_num])(params, args);
-      }
-    }
-    return TypeInstance::Dispatch(self, label, params, args);
-  }
-  ReturnTuple Call_default(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_builder(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_equals(const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_lessThan(const ParamTuple& params, const ValueTuple& args);
-};
-S<Type_String> CreateType_String(Params<0>::Type params) {
-  static const auto cached = S_get(new Type_String(CreateCategory_String(), Params<0>::Type()));
-  return cached;
-}
-struct Value_String : public TypeValue {
-  Value_String(S<Type_String> p, const PrimString& value) : parent(p), value_(value) {}
-  ReturnTuple Dispatch(const S<TypeValue>& self, const ValueFunction& label, const ParamTuple& params,const ValueTuple& args) final {
-    using CallType = ReturnTuple(Value_String::*)(const S<TypeValue>&, const ParamTuple&, const ValueTuple&);
-    static const CallType Table_AsBool[] = {
-      &Value_String::Call_asBool,
-    };
-    static const CallType Table_DefaultOrder[] = {
-      &Value_String::Call_defaultOrder,
-    };
-    static const CallType Table_Formatted[] = {
-      &Value_String::Call_formatted,
-    };
-    static const CallType Table_ReadAt[] = {
-      &Value_String::Call_readAt,
-    };
-    static const CallType Table_Container[] = {
-      &Value_String::Call_size,
-    };
-    static const CallType Table_String[] = {
-      &Value_String::Call_subSequence,
-    };
-    static const CallType Table_SubSequence[] = {
-      &Value_String::Call_subSequence,
-    };
-    static DispatchTable<CallType> all_tables[] = {
-      DispatchTable<CallType>(Functions_AsBool,       Table_AsBool),
-      DispatchTable<CallType>(Functions_Container,    Table_Container),
-      DispatchTable<CallType>(Functions_DefaultOrder, Table_DefaultOrder),
-      DispatchTable<CallType>(Functions_Formatted,    Table_Formatted),
-      DispatchTable<CallType>(Functions_ReadAt,       Table_ReadAt),
-      DispatchTable<CallType>(Functions_String,       Table_String),
-      DispatchTable<CallType>(Functions_SubSequence,  Table_SubSequence),
-      DispatchTable<CallType>(),
-    };
-    std::atomic_bool table_lock{0};
-    const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);
-    if (table) {
-      if (label.function_num < 0 || label.function_num >= table->size) {
-        FAIL() << "Bad function call " << label;
-      } else {
-        return (this->*table->table[label.function_num])(self, params, args);
-      }
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-  std::string CategoryName() const final { return parent->CategoryName(); }
-  const PrimString& AsString() const final { return value_; }
-  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_defaultOrder(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_readAt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_size(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  ReturnTuple Call_subSequence(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args);
-  const S<Type_String> parent;
-  const PrimString value_;
-};
-
-class Value_StringBuilder : public TypeValue {
- public:
-  std::string CategoryName() const final { return "StringBuilder"; }
-
-  ReturnTuple Dispatch(const S<TypeValue>& self,
-                       const ValueFunction& label,
-                       const ParamTuple& params, const ValueTuple& args) final {
-    if (args.Size() != label.arg_count) {
-      FAIL() << "Wrong number of args";
-    }
-    if (params.Size() != label.param_count){
-      FAIL() << "Wrong number of params";
-    }
-    if (&label == &Function_Append_append) {
-      TRACE_FUNCTION("StringBuilder.append")
-      std::lock_guard<std::mutex> lock(mutex);
-      output_ << args.At(0)->AsString();
-      return ReturnTuple(self);
-    }
-    if (&label == &Function_Build_build) {
-      TRACE_FUNCTION("StringBuilder.build")
-      std::lock_guard<std::mutex> lock(mutex);
-      return ReturnTuple(Box_String(output_.str()));
-    }
-    return TypeValue::Dispatch(self, label, params, args);
-  }
-
- private:
-  std::mutex mutex;
-  std::ostringstream output_;
-};
-
-struct StringOrder : public AnonymousOrder {
-  StringOrder(S<TypeValue> container, const std::string& s)
-    : AnonymousOrder(container, Function_Order_next, Function_Order_get), value(s) {}
-
-  S<TypeValue> Call_next(const S<TypeValue>& self) final {
-    if (index+1 >= value.size()) {
-      return Var_empty;
-    } else {
-      ++index;
-      return self;
-    }
-  }
-
-  S<TypeValue> Call_get(const S<TypeValue>& self) final {
-    if (index >= value.size()) FAIL() << "iterated past end of String";
-    return Box_Char(value[index]);
-  }
-
-  const std::string& value;
-  int index = 0;
-};
-
-ReturnTuple Type_String::Call_default(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.default")
-  return ReturnTuple(Box_String(""));
-}
-ReturnTuple Type_String::Call_builder(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.builder")
-  return ReturnTuple(S<TypeValue>(new Value_StringBuilder));
-}
-ReturnTuple Type_String::Call_equals(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.equals")
-  const S<TypeValue>& Var_arg1 = (args.At(0));
-  const S<TypeValue>& Var_arg2 = (args.At(1));
-  return ReturnTuple(Box_Bool(Var_arg1->AsString()==Var_arg2->AsString()));
-}
-ReturnTuple Type_String::Call_lessThan(const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.lessThan")
-  const S<TypeValue>& Var_arg1 = (args.At(0));
-  const S<TypeValue>& Var_arg2 = (args.At(1));
-  return ReturnTuple(Box_Bool(Var_arg1->AsString()<Var_arg2->AsString()));
-}
-ReturnTuple Value_String::Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.asBool")
-  return ReturnTuple(Box_Bool(value_.size() != 0));
-}
-ReturnTuple Value_String::Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.formatted")
-  return ReturnTuple(Var_self);
-}
-ReturnTuple Value_String::Call_defaultOrder(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.defaultOrder")
-  if (value_.empty()) {
-    return ReturnTuple(Var_empty);
-  } else {
-    return ReturnTuple(S_get(new StringOrder(Var_self, value_)));
-  }
-}
-ReturnTuple Value_String::Call_readAt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.readAt")
-  const PrimInt Var_arg1 = (args.At(0))->AsInt();
-  if (Var_arg1 < 0 || Var_arg1 >= value_.size()) {
-    FAIL() << "Read position " << Var_arg1 << " is out of bounds";
-  }
-  return ReturnTuple(Box_Char(value_[Var_arg1]));
-}
-ReturnTuple Value_String::Call_size(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.size")
-  return ReturnTuple(Box_Int(value_.size()));
-}
-ReturnTuple Value_String::Call_subSequence(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) {
-  TRACE_FUNCTION("String.subSequence")
-  const PrimInt Var_arg1 = (args.At(0))->AsInt();
-  const PrimInt Var_arg2 = (args.At(1))->AsInt();
-  if (Var_arg1 < 0 || Var_arg1 > value_.size()) {
-    FAIL() << "Subsequence position " << Var_arg1 << " is out of bounds";
-  }
-  if (Var_arg2 < 0 || Var_arg1 + Var_arg2 > value_.size()) {
-    FAIL() << "Subsequence size " << Var_arg2 << " is invalid";
-  }
-  return ReturnTuple(Box_String(value_.substr(Var_arg1,Var_arg2)));
-}
-}  // namespace
-TypeCategory& GetCategory_String() {
-  return CreateCategory_String();
-}
-S<TypeInstance> GetType_String(Params<0>::Type params) {
-  return CreateType_String(params);
-}
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-
-S<TypeValue> Box_String(const PrimString& value) {
-  return S_get(new Value_String(CreateType_String(Params<0>::Type()), value));
-}
diff --git a/base/builtin.0rp b/base/builtin.0rp
--- a/base/builtin.0rp
+++ b/base/builtin.0rp
@@ -102,6 +102,14 @@
   defines Equals<String>
   defines LessThan<String>
 
+  // Create a new String as an immutable copy of a CharBuffer.
+  //
+  // Notes:
+  // - Changing the data in the CharBuffer after copying will not result in
+  //   modifications to the String returned here.
+  // - Data will be copied verbatim, even if some characters are null.
+  @type fromCharBuffer (CharBuffer) -> (String)
+
   // Get a builder to create a string using concatenation.
   //
   // Example:
@@ -110,6 +118,28 @@
   @type builder () -> ([Append<String>&Build<String>])
 }
 
+// Built-in fixed-size buffer of byte data.
+//
+// Literals: None.
+concrete CharBuffer {
+  refines ReadAt<Char>
+  refines WriteAt<Char>
+
+  // Create a new buffer with a fixed size.
+  //
+  // Notes:
+  // - The implementation does not guarantee that the values will be zeroed.
+  @type new (Int) -> (CharBuffer)
+
+  // Resize the buffer to the specified size.
+  //
+  // Notes:
+  // - The implementation does not guarantee that the values will be zeroed.
+  //   Specifically, resizing down and then back up again could either preserve
+  //   or overwrite any of the data.
+  @value resize (Int) -> (CharBuffer)
+}
+
 // Convertible to Bool.
 @value interface AsBool {
   // Convert the value to Bool.
@@ -203,6 +233,17 @@
 
   // Return the value at the given index.
   readAt (Int) -> (#x)
+}
+
+// Random-access writing to a container.
+//
+// Params:
+// - #x: The value type contained.
+@value interface WriteAt<#x|> {
+  refines Container
+
+  // Write the value at the given index and return self.
+  writeAt (Int,#x) -> (#self)
 }
 
 // Sequence that can be subsequenced.
diff --git a/base/category-source.cpp b/base/category-source.cpp
deleted file mode 100644
--- a/base/category-source.cpp
+++ /dev/null
@@ -1,292 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2021 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-#include "category-source.hpp"
-
-#include <algorithm>
-
-#include "logging.hpp"
-
-
-namespace {
-
-struct OptionalEmpty : public TypeValue {
-  ReturnTuple Dispatch(const S<TypeValue>& self,
-                       const ValueFunction& label,
-                       const ParamTuple& params, const ValueTuple& args) final {
-    FAIL() << "Function called on empty value";
-    __builtin_unreachable();
-  }
-
-  std::string CategoryName() const final { return "empty"; }
-
-  bool Present() const final { return false; }
-};
-
-struct Type_Intersect : public TypeInstance {
-  Type_Intersect(L<S<const TypeInstance>> params) : params_(params.begin(), params.end()) {}
-
-  std::string CategoryName() const final { return "(intersection)"; }
-
-  void BuildTypeName(std::ostream& output) const final {
-    if (params_.empty()) {
-      output << "any";
-    } else {
-      output << "[";
-      bool first = true;
-      for (const auto param : params_) {
-        if (!first) output << "&";
-        first = false;
-        param->BuildTypeName(output);
-      }
-      output << "]";
-    }
-  }
-
-  MergeType InstanceMergeType() const final
-  { return MergeType::INTERSECT; }
-
-  std::vector<S<const TypeInstance>> MergedTypes() const final
-  { return params_; }
-
-  const L<S<const TypeInstance>> params_;
-};
-
-struct Type_Union : public TypeInstance {
-  Type_Union(L<S<const TypeInstance>> params) : params_(params.begin(), params.end()) {}
-
-  std::string CategoryName() const final { return "(union)"; }
-
-  void BuildTypeName(std::ostream& output) const final {
-    if (params_.empty()) {
-      output << "all";
-    } else {
-      output << "[";
-      bool first = true;
-      for (const auto param : params_) {
-        if (!first) output << "|";
-        first = false;
-        param->BuildTypeName(output);
-      }
-      output << "]";
-    }
-  }
-
-  MergeType InstanceMergeType() const final
-  { return MergeType::UNION; }
-
-  std::vector<S<const TypeInstance>> MergedTypes() const final
-  { return params_; }
-
-  const L<S<const TypeInstance>> params_;
-};
-
-L<const TypeInstance*> ParamsToKey(const L<S<const TypeInstance>>& params) {
-  L<const TypeInstance*> key;
-  for (const auto& param : params) {
-    key.push_back(param.get());
-  }
-  std::sort(key.begin(), key.end());
-  return key;
-}
-
-}  // namespace
-
-
-S<TypeInstance> Merge_Intersect(L<S<const TypeInstance>> params) {
-  static auto& cache = *new std::map<L<const TypeInstance*>,S<Type_Intersect>>();
-  auto& cached = cache[ParamsToKey(params)];
-  S<Type_Intersect> type = cached;
-  if (!type) { cached = type = S_get(new Type_Intersect(params)); }
-  return type;
-}
-
-S<TypeInstance> Merge_Union(L<S<const TypeInstance>> params) {
-  static auto& cache = *new std::map<L<const TypeInstance*>,S<Type_Union>>();
-  auto& cached = cache[ParamsToKey(params)];
-  S<Type_Union> type = cached;
-  if (!type) { cached = type = S_get(new Type_Union(params)); }
-  return type;
-}
-
-const S<TypeInstance>& GetMerged_Any() {
-  static const auto instance = Merge_Intersect(L_get<S<const TypeInstance>>());
-  return instance;
-}
-
-const S<TypeInstance>& GetMerged_All() {
-  static const auto instance = Merge_Union(L_get<S<const TypeInstance>>());
-  return instance;
-}
-
-
-const S<TypeValue>& Var_empty = *new S<TypeValue>(new OptionalEmpty());
-
-
-ReturnTuple TypeCategory::Dispatch(const CategoryFunction& label,
-                                   const ParamTuple& params, const ValueTuple& args) {
-  FAIL() << CategoryName() << " does not implement " << label;
-  __builtin_unreachable();
-}
-
-ReturnTuple TypeInstance::Dispatch(const S<TypeInstance>& self, const TypeFunction& label,
-                                   const ParamTuple& params, const ValueTuple& args) {
-  FAIL() << CategoryName() << " does not implement " << label;
-  __builtin_unreachable();
-}
-
-ReturnTuple TypeValue::Dispatch(const S<TypeValue>& self, const ValueFunction& label,
-                                const ParamTuple& params, const ValueTuple& args) {
-  FAIL() << CategoryName() << " does not implement " << label;
-  __builtin_unreachable();
-}
-
-bool TypeInstance::CanConvert(const S<const TypeInstance>& x,
-                              const S<const TypeInstance>& y) {
-  // See pairMergeTree for the ordering here.
-  // TODO: Consider using a cache here, since the check could be expensive.
-  if (x.get() == y.get()) {
-    return true;
-  } else if (x->InstanceMergeType() == MergeType::INTERSECT &&
-             y->InstanceMergeType() == MergeType::UNION) {
-    for (const auto& left : x->MergedTypes()) {
-      if (left->InstanceMergeType() == MergeType::SINGLE) {
-        for (const auto& right : y->MergedTypes()) {
-          if (right->InstanceMergeType() == MergeType::SINGLE) {
-            if (TypeInstance::CanConvert(left, right)) {
-              return true;
-            }
-          }
-        }
-      }
-    }
-    for (const auto& left : x->MergedTypes()) {
-      if (TypeInstance::CanConvert(left, y)) {
-        return true;
-      }
-    }
-    for (const auto right : y->MergedTypes()) {
-      if (TypeInstance::CanConvert(x, right)) {
-        return true;
-      }
-    }
-    return false;
-  } else if (y->InstanceMergeType() == MergeType::INTERSECT) {
-    for (const auto& right : y->MergedTypes()) {
-      if (!TypeInstance::CanConvert(x, right)) {
-        return false;
-      }
-    }
-    return true;
-  } else if (x->InstanceMergeType() == MergeType::UNION) {
-    for (const auto& left : x->MergedTypes()) {
-      if (!TypeInstance::CanConvert(left, y)) {
-        return false;
-      }
-    }
-    return true;
-  } else if (x->InstanceMergeType() == MergeType::INTERSECT) {
-    for (const auto& left : x->MergedTypes()) {
-      if (TypeInstance::CanConvert(left, y)) {
-        return true;
-      }
-    }
-    return false;
-  } else if (y->InstanceMergeType() == MergeType::UNION) {
-    for (const auto right : y->MergedTypes()) {
-      if (TypeInstance::CanConvert(x, right)) {
-        return true;
-      }
-    }
-    return false;
-  } else {
-    return y->CanConvertFrom(x);
-  }
-}
-
-bool TypeValue::Present(S<TypeValue> target) {
-  if (target == nullptr) {
-    FAIL() << "Builtin called on null value";
-  }
-  return target->Present();
-}
-
-S<TypeValue> TypeValue::Require(S<TypeValue> target) {
-  if (target == nullptr) {
-    FAIL() << "Builtin called on null value";
-  }
-  if (!target->Present()) {
-    FAIL() << "Cannot require empty value";
-  }
-  return target;
-}
-
-S<TypeValue> TypeValue::Strong(W<TypeValue> target) {
-  const auto strong = target.lock();
-  return strong? strong : Var_empty;
-}
-
-bool TypeValue::AsBool() const {
-  FAIL() << CategoryName() << " is not a Bool value";
-  __builtin_unreachable();
-}
-
-const PrimString& TypeValue::AsString() const {
-  FAIL() << CategoryName() << " is not a String value";
-  __builtin_unreachable();
-}
-
-PrimChar TypeValue::AsChar() const {
-  FAIL() << CategoryName() << " is not a Char value";
-  __builtin_unreachable();
-}
-
-PrimInt TypeValue::AsInt() const {
-  FAIL() << CategoryName() << " is not an Int value";
-  __builtin_unreachable();
-}
-
-PrimFloat TypeValue::AsFloat() const {
-  FAIL() << CategoryName() << " is not a Float value";
-  __builtin_unreachable();
-}
-
-bool TypeValue::Present() const {
-  return true;
-}
-
-AnonymousOrder::AnonymousOrder(const S<TypeValue> cont,
-                               const ValueFunction& func_next,
-                               const ValueFunction& func_get)
-  : container(cont), function_next(func_next), function_get(func_get) {}
-
-std::string AnonymousOrder::CategoryName() const { return "AnonymousOrder"; }
-
-ReturnTuple AnonymousOrder::Dispatch(
-  const S<TypeValue>& self, const ValueFunction& label,
-  const ParamTuple& params, const ValueTuple& args) {
-  if (&label == &function_next) {
-    TRACE_FUNCTION("AnonymousOrder.next")
-    return ReturnTuple(Call_next(self));
-  }
-  if (&label == &function_get) {
-    TRACE_FUNCTION("AnonymousOrder.get")
-    return ReturnTuple(Call_get(self));
-  }
-  return TypeValue::Dispatch(self, label, params, args);
-}
diff --git a/base/category-source.hpp b/base/category-source.hpp
--- a/base/category-source.hpp
+++ b/base/category-source.hpp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2020 Kevin P. Barry
+Copyright 2019-2021 Kevin P. Barry
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -19,17 +19,13 @@
 #ifndef CATEGORY_SOURCE_HPP_
 #define CATEGORY_SOURCE_HPP_
 
-#include <algorithm>
-#include <atomic>
 #include <iostream>  // For occasional debugging output in generated code.
 #include <map>
-#include <mutex>
 #include <sstream>
 #include <vector>
 
 #include "types.hpp"
 #include "function.hpp"
-#include "cycle-check.hpp"
 
 
 #define BUILTIN_FAIL(e) { \
@@ -176,6 +172,7 @@
   virtual bool AsBool() const;
   virtual const PrimString& AsString() const;
   virtual PrimChar AsChar() const;
+  virtual PrimCharBuffer& AsCharBuffer();
   virtual PrimInt AsInt() const;
   virtual PrimFloat AsFloat() const;
 
@@ -194,29 +191,6 @@
                                const ParamTuple& params, const ValueTuple& args);
 };
 
-template <int P, class T>
-class InstanceCache {
- public:
-  using Creator = std::function<S<T>(typename Params<P>::Type)>;
-
-  InstanceCache(const Creator& create) : create_(create) {}
-
-  S<T> GetOrCreate(typename Params<P>::Type params) {
-    std::lock_guard<std::mutex> lock(mutex_);
-    auto& cached = cache_[GetKeyFromParams<P>(params)];
-    S<T> type = cached;
-    if (!type) {
-      cached = type = create_(params);
-    }
-    return type;
-  }
-
- private:
-  const Creator create_;
-  std::mutex mutex_;
-  std::map<typename ParamsKey<P>::Type, S<T>> cache_;
-};
-
 class AnonymousOrder : public TypeValue {
  protected:
   // Passing in the function labels allows linking without depending on Order
@@ -241,54 +215,5 @@
   const ValueFunction& function_next;
   const ValueFunction& function_get;
 };
-
-template<class F>
-struct DispatchTable {
-  constexpr DispatchTable() : key(nullptr), table(nullptr), size(0) {}
-
-  template<int S>
-  DispatchTable(const void* k, const F(&t)[S]) : key(k), table(t), size(S) {}
-
-  bool operator < (const DispatchTable<F>& other) const { return key < other.key; }
-
-  const void* key;
-  const F* table;
-  int size;
-};
-
-template<class F>
-struct DispatchSingle {
-  constexpr DispatchSingle() : key(nullptr), value() {}
-
-  DispatchSingle(const void* k, const F v) : key(k), value(v) {}
-
-  bool operator < (const DispatchSingle<F>& other) const { return key < other.key; }
-
-  const void* key;
-  F value;
-  int size;
-};
-
-template<class T, int S>
-const T* DispatchSelect(std::atomic_bool& lock, const void* key, T(&table)[S]) {
-  if (S < 2) return nullptr;
-  while (lock.exchange(true));
-  if (table[0].key != nullptr) {
-    std::sort(table, table+S);
-  }
-  lock.exchange(false);
-  int i = 1, j = S;
-  while (j-i > 1) {
-    const int k = (i+j)/2;
-    if (table[k].key < key) {
-      i = k;
-    } else if (table[k].key > key) {
-      j = k;
-    } else {
-      return &table[k];
-    }
-  }
-  return (table[i].key == key)? &table[i] : nullptr;
-}
 
 #endif  // CATEGORY_SOURCE_HPP_
diff --git a/base/function.hpp b/base/function.hpp
--- a/base/function.hpp
+++ b/base/function.hpp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2020 Kevin P. Barry
+Copyright 2019-2021 Kevin P. Barry
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
   const int return_count;
   const char* const category;
   const char* const function;
-  const void* const collection;
+  const CollectionType collection;
   const int function_num;
 };
 
@@ -38,7 +38,7 @@
   const int return_count;
   const char* const category;
   const char* const function;
-  const void* const collection;
+  const CollectionType collection;
   const int function_num;
 };
 
@@ -48,12 +48,12 @@
   const int return_count;
   const char* const category;
   const char* const function;
-  const void* const collection;
+  const CollectionType collection;
   const int function_num;
 };
 
 inline std::ostream& operator << (std::ostream& output, const CategoryFunction& func) {
-  return output << func.category << "." << func.function;
+  return output << func.category << ":" << func.function;
 }
 
 inline std::ostream& operator << (std::ostream& output, const TypeFunction& func) {
diff --git a/base/internal.hpp b/base/internal.hpp
new file mode 100644
--- /dev/null
+++ b/base/internal.hpp
@@ -0,0 +1,151 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#ifndef INTERNAL_HPP_
+#define INTERNAL_HPP_
+
+#include <algorithm>
+#include <atomic>
+
+#include "category-source.hpp"
+#include "cycle-check.hpp"
+
+
+#define COLLECTION_ID(vp) (int) 1000000009 * (long) (vp)
+
+template <int P, class T>
+class InstanceCache {
+ public:
+  using Creator = std::function<S<T>(typename Params<P>::Type)>;
+
+  InstanceCache(const Creator& create) : create_(create) {}
+
+  S<T> GetOrCreate(typename Params<P>::Type params) {
+    while (lock_.test_and_set(std::memory_order_acquire));
+    auto& cached = cache_[GetKeyFromParams<P>(params)];
+    S<T> type = cached;
+    if (!type) {
+      cached = type = create_(params);
+    }
+    lock_.clear(std::memory_order_release);
+    return type;
+  }
+
+ private:
+  const Creator create_;
+  std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
+  std::map<typename ParamsKey<P>::Type, S<T>> cache_;
+};
+
+template<class F>
+struct DispatchTable {
+  constexpr DispatchTable() : key(), table(nullptr), size(0) {}
+
+  template<int S>
+  DispatchTable(CollectionType k, const F(&t)[S]) : key(k), table(t), size(S) {}
+
+  inline bool operator < (const DispatchTable<F>& other) const { return key < other.key; }
+
+  CollectionType key;
+  const F* table;
+  int size;
+};
+
+template<class F>
+struct DispatchSingle {
+  constexpr DispatchSingle() : key(nullptr), value() {}
+
+  DispatchSingle(const void* k, const F v) : key(k), value(v) {}
+
+  inline bool operator < (const DispatchSingle<F>& other) const { return key < other.key; }
+
+  const void* key;
+  F value;
+};
+
+struct StaticSort {
+  template<class T, int S>
+  StaticSort(T(&table)[S]) {
+    std::sort(table, table+S);
+  }
+};
+
+template<class T>
+struct LoopLimit {};
+
+template<class F>
+struct LoopLimit<DispatchTable<F>> {
+  // See function-calls.0rt for tests.
+  static constexpr int max = 15;
+};
+
+template<class F>
+struct LoopLimit<DispatchSingle<F>> {
+  // See function-calls.0rt for tests.
+  static constexpr int max = 15;
+};
+
+template<bool L, class T, int S>
+struct DispatchChoice {};
+
+template<class T>
+struct DispatchChoice<true, T, 0> {
+  template<class K>
+  static const T* Select(K key, T* table) {
+    return nullptr;
+  }
+};
+template<class T, int S>
+struct DispatchChoice<true, T, S> {
+  template<class K>
+  static const T* Select(K key, T* table) {
+    if (table->key == key) {
+      return table;
+    }
+    return DispatchChoice<true, T, S-1>::Select(key, table+1);
+  }
+};
+
+template<class T, int S>
+struct DispatchChoice<false, T, S> {
+  template<class K>
+  static const T* Select(K key, T* table) {
+    int i = 0, j = S, k;
+    while ((k = (i+j)/2) > i) {
+      if (table[k].key < key) {
+        i = k;
+      } else if (table[k].key > key) {
+        j = k;
+      } else {
+        return &table[k];
+      }
+    }
+    if (table[k].key == key) {
+      return &table[k];
+    } else {
+      return nullptr;
+    }
+  }
+};
+
+template<class K, class T, int S>
+const T* DispatchSelect(K key, T(&table)[S]) {
+  return DispatchChoice<(S <= LoopLimit<T>::max), T, S>::Select(key, table);
+}
+
+#endif  // INTERNAL_HPP_
diff --git a/base/logging.cpp b/base/logging.cpp
deleted file mode 100644
--- a/base/logging.cpp
+++ /dev/null
@@ -1,234 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019-2020 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-#include "logging.hpp"
-
-#include <cassert>
-#include <chrono>
-#include <csignal>
-#include <iomanip>
-#include <iostream>
-
-
-LogThenCrash::LogThenCrash(bool fail, const std::string& condition)
-    : fail_(fail), signal_(SIGABRT), condition_(condition) {}
-
-LogThenCrash::LogThenCrash(bool fail, int signal)
-    : fail_(fail), signal_(signal) {}
-
-LogThenCrash::~LogThenCrash() {
-  if (fail_) {
-    std::signal(signal_, SIG_DFL);
-    if (Argv::ArgCount() > 0) {
-      std::cerr << Argv::GetArgAt(0) << ": ";
-    }
-    std::cerr << "Failed condition";
-    if (!condition_.empty()) {
-      std::cerr << " '" << condition_ << "'";
-    }
-    std::cerr << ": " << output_.str() << std::endl;
-    PrintTrace(TraceContext::GetTrace());
-    const TraceList creation_trace = TraceCreation::GetTrace();
-    if (!creation_trace.empty()) {
-      std::cerr << TraceCreation::GetType() << " value originally created at:" << std::endl;
-      PrintTrace(creation_trace);
-    }
-    std::raise(signal_);
-  }
-}
-
-// static
-void LogThenCrash::PrintTrace(const TraceList &call_trace) {
-  for (const auto& trace : call_trace) {
-    const std::string message = trace();
-    if (!message.empty()) {
-      std::cerr << "  " << message << std::endl;
-    }
-  }
-}
-
-// TODO: Should only be available used if POSIX is defined.
-void TraceOnSignal(int signal) {
-  LogThenCrash(true,signal) << "Recieved signal " << signal;
-}
-
-// TODO: Should only be available used if POSIX is defined.
-void SetSignalHandler() {
-#ifdef SIGINT
-  std::signal(SIGINT, &TraceOnSignal);
-#endif
-#ifdef SIGILL
-  std::signal(SIGILL, &TraceOnSignal);
-#endif
-#ifdef SIGABRT
-  std::signal(SIGABRT, &TraceOnSignal);
-#endif
-#ifdef SIGFPE
-  std::signal(SIGFPE, &TraceOnSignal);
-#endif
-#ifdef SIGQUIT
-  std::signal(SIGQUIT, &TraceOnSignal);
-#endif
-#ifdef SIGSEGV
-  std::signal(SIGSEGV, &TraceOnSignal);
-#endif
-#ifdef SIGPIPE
-  std::signal(SIGPIPE, &TraceOnSignal);
-#endif
-#ifdef SIGALRM
-  std::signal(SIGALRM, &TraceOnSignal);
-#endif
-#ifdef SIGTERM
-  std::signal(SIGTERM, &TraceOnSignal);
-#endif
-#ifdef SIGUSR1
-  std::signal(SIGUSR1, &TraceOnSignal);
-#endif
-#ifdef SIGUSR2
-  std::signal(SIGUSR2, &TraceOnSignal);
-#endif
-}
-
-
-TraceList TraceContext::GetTrace() {
-  TraceList trace;
-  const TraceContext* current = GetCurrent();
-  while (current) {
-    current->AppendTrace(trace);
-    current = current->GetNext();
-  }
-  return trace;
-}
-
-
-void SourceContext::SetLocal(const char* at) {
-  at_ = at;
-  LogCalls::MaybeLogCall(name_, at_);
-}
-
-void SourceContext::AppendTrace(TraceList& trace) const {
-  const char* const name = name_;
-  const char* const at   = at_;
-  trace.push_back([name,at]() {
-      std::ostringstream output;
-      if (at == nullptr || at[0] == 0x00) {
-        output << "From " << name;
-      } else {
-        output << "From " << name << " at " << at;
-      }
-      return output.str();
-    });
-}
-
-const TraceContext* SourceContext::GetNext() const {
-  return capture_to_.Previous();
-}
-
-
-void CleanupContext::SetLocal(const char* at) {
-  at_ = at;
-}
-
-void CleanupContext::AppendTrace(TraceList& trace) const {
-  const char* const at = at_;
-  trace.push_back([at]() {
-      std::ostringstream output;
-      if (at == nullptr || at[0] == 0x00) {
-        output << "In cleanup block";
-      } else {
-        output << "In cleanup block at " << at;
-      }
-      return output.str();
-    });
-}
-
-const TraceContext* CleanupContext::GetNext() const {
-  return capture_to_.Previous();
-}
-
-int Argv::ArgCount() {
-  if (GetCurrent()) {
-    return GetCurrent()->GetArgs().size();
-  } else {
-    return 0;
-  }
-}
-
-const std::string& Argv::GetArgAt(int pos) {
-  if (pos < 0 || pos >= ArgCount()) {
-    FAIL() << "Argv index " << pos << " is out of bounds";
-    __builtin_unreachable();
-  } else {
-    return GetCurrent()->GetArgs()[pos];
-  }
-}
-
-const std::vector<std::string>& ProgramArgv::GetArgs() const {
-  return argv_;
-}
-
-std::string FixCsvString(const char* string) {
-  std::string fixed;
-  while (*string) {
-    switch (*string) {
-      case '\\':
-        break;
-      case '"':
-        fixed.push_back('\'');
-        break;
-      default:
-        fixed.push_back(*string);
-        break;
-    }
-    ++string;
-  }
-  return fixed;
-}
-
-unsigned int UniqueId() {
-  const auto time = std::chrono::steady_clock::now().time_since_epoch();
-  return (1000000009 * std::chrono::duration_cast<std::chrono::microseconds>(time).count());
-}
-
-LogCallsToFile::LogCallsToFile(std::string filename)
-  : unique_id_(UniqueId()),
-    filename_(std::move(filename)),
-    log_file_(filename_.empty()?
-                nullptr :
-                new std::fstream(filename_, std::ios::in |
-                                            std::ios::out |
-                                            std::ios::ate |
-                                            std::ios::app)),
-    cross_and_capture_to_(this) {
-  if (log_file_) {
-    if (!*log_file_) {
-      FAIL() << "Failed to open call log " << filename_ << " for writing";
-    }
-  }
-}
-
-void LogCallsToFile::LogCall(const char* name, const char* at) {
-  if (log_file_) {
-    std::lock_guard<std::mutex> lock(mutex_);
-    const auto time = std::chrono::steady_clock::now().time_since_epoch();
-    *log_file_ << std::chrono::duration_cast<std::chrono::microseconds>(time).count() << ","
-               << unique_id_ << ","
-               << "\"" << FixCsvString(name) << "\"" << ","
-               << "\"" << FixCsvString(at) << "\"" << std::endl;
-  }
-}
diff --git a/base/pooled.hpp b/base/pooled.hpp
new file mode 100644
--- /dev/null
+++ b/base/pooled.hpp
@@ -0,0 +1,109 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#ifndef POOLED_HPP_
+#define POOLED_HPP_
+
+
+template<class T>
+class PoolStorage {
+ public:
+  inline T* data() const {
+    return (T*) (this + 1);
+  }
+
+  const int size = 0;
+  PoolStorage* next = nullptr;
+
+private:
+  template<class> friend class PoolManager;
+
+  inline PoolStorage(int s, PoolStorage* n) : size(s), next(n) {}
+
+  inline ~PoolStorage() {}
+
+  PoolStorage(const PoolStorage&) = delete;
+  PoolStorage& operator = (const PoolStorage&) = delete;
+  PoolStorage(PoolStorage&&) = delete;
+  PoolStorage& operator = (PoolStorage&&) = delete;
+};
+
+template<class T>
+struct PoolManager {};
+
+template<class T>
+class PoolArray {
+  friend class ArgTuple;
+  friend class ReturnTuple;
+  friend class ParamTuple;
+
+  constexpr PoolArray() : array_(nullptr) {}
+
+  PoolArray(int size) : array_(PoolManager<T>::Take(size)) {}
+
+  PoolArray(PoolArray&& other) : array_(other.array_) {
+    other.array_ = nullptr;
+  }
+
+  PoolArray& operator = (PoolArray&& other) {
+    array_ = other.array_;
+    other.array_ = nullptr;
+    return *this;
+  }
+
+  const T& operator [] (int i) const {
+    if (!array_ || i < 0 || i >= array_->size) {
+      FAIL() << "Bad array index " << i;
+    }
+    return array_->data()[i];
+  }
+
+  T& operator [] (int i) {
+    if (!array_ || i < 0 || i >= array_->size) {
+      FAIL() << "Bad array index " << i;
+    }
+    return array_->data()[i];
+  }
+
+  template<class...Ts>
+  inline void Init(Ts... data) {
+    if (sizeof...(Ts) > array_->size) {
+      FAIL() << "Too many init values " << sizeof...(Ts);
+    }
+    InitRec(0, std::move(data)...);
+  }
+
+  template<class...Ts>
+  inline void InitRec(int i, T arg, Ts... data) {
+    array_->data()[i] = std::move(arg);
+    InitRec(i+1, std::move(data)...);
+  }
+
+  inline void InitRec(int i) {}
+
+  ~PoolArray() {
+    PoolManager<T>::Return(array_);
+  }
+
+  PoolArray(const PoolArray&) = delete;
+  PoolArray& operator = (const PoolArray&) = delete;
+
+  PoolStorage<T>* array_;
+};
+
+#endif  // POOLED_HPP_
diff --git a/base/src/Extension_Bool.cpp b/base/src/Extension_Bool.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Extension_Bool.cpp
@@ -0,0 +1,102 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "category-source.hpp"
+#include "Streamlined_Bool.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_Bool.hpp"
+#include "Category_Default.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Float.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Int.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_Bool : public Category_Bool {
+};
+
+struct ExtType_Bool : public Type_Bool {
+  inline ExtType_Bool(Category_Bool& p, Params<0>::Type params) : Type_Bool(p, params) {}
+
+  ReturnTuple Call_default(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Bool.default")
+    return ReturnTuple(Box_Bool(false));
+  }
+
+  ReturnTuple Call_equals(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Bool.equals")
+    const bool Var_arg1 = (args.At(0))->AsBool();
+    const bool Var_arg2 = (args.At(1))->AsBool();
+    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+  }
+};
+
+struct ExtValue_Bool : public Value_Bool {
+  inline ExtValue_Bool(S<Type_Bool> p, bool value) : Value_Bool(p), value_(value) {}
+
+  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Bool.asBool")
+    return ReturnTuple(Var_self);
+  }
+
+  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Bool.asFloat")
+    return ReturnTuple(Box_Float(value_ ? 1.0 : 0.0));
+  }
+
+  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Bool.asInt")
+    return ReturnTuple(Box_Int(value_? 1 : 0));
+  }
+
+  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Bool.formatted")
+    return ReturnTuple(Box_String(value_? "true" : "false"));
+  }
+
+  bool AsBool() const final { return value_; }
+
+  const bool value_;
+};
+
+const S<TypeValue>& Var_true = *new S<TypeValue>(new ExtValue_Bool(CreateType_Bool(Params<0>::Type()), true));
+const S<TypeValue>& Var_false = *new S<TypeValue>(new ExtValue_Bool(CreateType_Bool(Params<0>::Type()), false));
+
+Category_Bool& CreateCategory_Bool() {
+  static auto& category = *new ExtCategory_Bool();
+  return category;
+}
+S<Type_Bool> CreateType_Bool(Params<0>::Type params) {
+  static const auto cached = S_get(new ExtType_Bool(CreateCategory_Bool(), Params<0>::Type()));
+  return cached;
+}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+S<TypeValue> Box_Bool(bool value) {
+  return value? Var_true : Var_false;
+}
diff --git a/base/src/Extension_Char.cpp b/base/src/Extension_Char.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Extension_Char.cpp
@@ -0,0 +1,114 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "category-source.hpp"
+#include "Streamlined_Char.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsChar.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_Bool.hpp"
+#include "Category_Char.hpp"
+#include "Category_Default.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Float.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Int.hpp"
+#include "Category_LessThan.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_Char : public Category_Char {
+};
+
+struct ExtType_Char : public Type_Char {
+  inline ExtType_Char(Category_Char& p, Params<0>::Type params) : Type_Char(p, params) {}
+
+  ReturnTuple Call_default(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Char.default")
+    return ReturnTuple(Box_Char('\0'));
+  }
+
+  ReturnTuple Call_equals(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Char.equals")
+    const PrimChar Var_arg1 = (args.At(0))->AsChar();
+    const PrimChar Var_arg2 = (args.At(1))->AsChar();
+    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+  }
+
+  ReturnTuple Call_lessThan(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Char.lessThan")
+    const PrimChar Var_arg1 = (args.At(0))->AsChar();
+    const PrimChar Var_arg2 = (args.At(1))->AsChar();
+    return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
+  }
+};
+
+struct ExtValue_Char : public Value_Char {
+  inline ExtValue_Char(S<Type_Char> p, PrimChar value) : Value_Char(p), value_(value) {}
+
+  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Char.asBool")
+    return ReturnTuple(Box_Bool(value_ != '\0'));
+  }
+
+  ReturnTuple Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Char.asChar")
+    return ReturnTuple(Var_self);
+  }
+
+  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Char.asFloat")
+    return ReturnTuple(Box_Float(value_));
+  }
+
+  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Char.asInt")
+    return ReturnTuple(Box_Int(value_));
+  }
+
+  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Char.formatted")
+    return ReturnTuple(Box_String(PrimString(1,value_)));
+  }
+
+  PrimChar AsChar() const final { return value_; }
+
+  const PrimChar value_;
+};
+
+Category_Char& CreateCategory_Char() {
+  static auto& category = *new ExtCategory_Char();
+  return category;
+}
+S<Type_Char> CreateType_Char(Params<0>::Type params) {
+  static const auto cached = S_get(new ExtType_Char(CreateCategory_Char(), Params<0>::Type()));
+  return cached;
+}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+S<TypeValue> Box_Char(PrimChar value) {
+  return S_get(new ExtValue_Char(CreateType_Char(Params<0>::Type()), value));
+}
diff --git a/base/src/Extension_CharBuffer.cpp b/base/src/Extension_CharBuffer.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Extension_CharBuffer.cpp
@@ -0,0 +1,110 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "category-source.hpp"
+#include "Streamlined_CharBuffer.hpp"
+#include "Category_Char.hpp"
+#include "Category_CharBuffer.hpp"
+#include "Category_Container.hpp"
+#include "Category_Int.hpp"
+#include "Category_ReadAt.hpp"
+#include "Category_WriteAt.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+S<TypeValue> CreateValue_CharBuffer(S<Type_CharBuffer> parent, PrimCharBuffer buffer);
+
+struct ExtCategory_CharBuffer : public Category_CharBuffer {
+};
+
+struct ExtType_CharBuffer : public Type_CharBuffer {
+  inline ExtType_CharBuffer(Category_CharBuffer& p, Params<0>::Type params) : Type_CharBuffer(p, params) {}
+
+  ReturnTuple Call_new(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("CharBuffer.new")
+    const PrimInt Var_arg1 = (args.At(0))->AsInt();
+    if (Var_arg1 < 0) {
+      FAIL() << "Buffer size " << Var_arg1 << " is invalid";
+    }
+    return ReturnTuple(CreateValue_CharBuffer(CreateType_CharBuffer(Params<0>::Type()), PrimCharBuffer(Var_arg1,'\0')));
+  }
+};
+
+struct ExtValue_CharBuffer : public Value_CharBuffer {
+  inline ExtValue_CharBuffer(S<Type_CharBuffer> p, PrimCharBuffer value) : Value_CharBuffer(p), value_(std::move(value)) {}
+
+  ReturnTuple Call_readAt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("CharBuffer.readAt")
+    const PrimInt Var_arg1 = (args.At(0))->AsInt();
+    if (Var_arg1 < 0 || Var_arg1 >= AsCharBuffer().size()) {
+      FAIL() << "Read position " << Var_arg1 << " is out of bounds";
+    }
+    return ReturnTuple(Box_Char(AsCharBuffer()[Var_arg1]));
+  }
+
+  ReturnTuple Call_resize(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("CharBuffer.resize")
+    const PrimInt Var_arg1 = (args.At(0))->AsInt();
+    if (Var_arg1 < 0) {
+      FAIL() << "Buffer size " << Var_arg1 << " is invalid";
+    } else {
+      value_.resize(Var_arg1);
+    }
+    return ReturnTuple(Var_self);
+  }
+
+  ReturnTuple Call_size(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("CharBuffer.size")
+    return ReturnTuple(Box_Int(value_.size()));
+  }
+
+  ReturnTuple Call_writeAt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("CharBuffer.writeAt")
+    const PrimInt Var_arg1 = (args.At(0))->AsInt();
+    const PrimChar Var_arg2 = (args.At(1))->AsChar();
+    if (Var_arg1 < 0 || Var_arg1 >= value_.size()) {
+      FAIL() << "Write position " << Var_arg1 << " is out of bounds";
+    } else {
+      value_[Var_arg1] = Var_arg2;
+    }
+    return ReturnTuple(Var_self);
+  }
+
+  PrimCharBuffer& AsCharBuffer() final { return value_; }
+
+  PrimCharBuffer value_;
+};
+
+Category_CharBuffer& CreateCategory_CharBuffer() {
+  static auto& category = *new ExtCategory_CharBuffer();
+  return category;
+}
+S<Type_CharBuffer> CreateType_CharBuffer(Params<0>::Type params) {
+  static const auto cached = S_get(new ExtType_CharBuffer(CreateCategory_CharBuffer(), Params<0>::Type()));
+  return cached;
+}
+S<TypeValue> CreateValue_CharBuffer(S<Type_CharBuffer> parent, PrimCharBuffer value) {
+  return S_get(new ExtValue_CharBuffer(parent, std::move(value)));
+}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/base/src/Extension_Float.cpp b/base/src/Extension_Float.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Extension_Float.cpp
@@ -0,0 +1,111 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include <sstream>
+
+#include "category-source.hpp"
+#include "Streamlined_Float.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_Bool.hpp"
+#include "Category_Default.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Float.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Int.hpp"
+#include "Category_LessThan.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_Float : public Category_Float {
+};
+
+struct ExtType_Float : public Type_Float {
+  inline ExtType_Float(Category_Float& p, Params<0>::Type params) : Type_Float(p, params) {}
+
+  ReturnTuple Call_default(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Float.default")
+    return ReturnTuple(Box_Float(0.0));
+  }
+
+  ReturnTuple Call_equals(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Float.equals")
+    const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+    const PrimFloat Var_arg2 = (args.At(1))->AsFloat();
+    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+  }
+
+  ReturnTuple Call_lessThan(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Float.lessThan")
+    const PrimFloat Var_arg1 = (args.At(0))->AsFloat();
+    const PrimFloat Var_arg2 = (args.At(1))->AsFloat();
+    return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
+  }
+};
+
+struct ExtValue_Float : public Value_Float {
+  inline ExtValue_Float(S<Type_Float> p, PrimFloat value) : Value_Float(p), value_(value) {}
+
+  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Float.asBool")
+    return ReturnTuple(Box_Bool(value_ != 0.0));
+  }
+
+  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Float.asFloat")
+    return ReturnTuple(Var_self);
+  }
+
+  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Float.asInt")
+    return ReturnTuple(Box_Int(value_));
+  }
+
+  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Float.formatted")
+    std::ostringstream output;
+    output << value_;
+    return ReturnTuple(Box_String(output.str()));
+  }
+
+  PrimFloat AsFloat() const final { return value_; }
+
+  const PrimFloat value_;
+};
+
+Category_Float& CreateCategory_Float() {
+  static auto& category = *new ExtCategory_Float();
+  return category;
+}
+S<Type_Float> CreateType_Float(Params<0>::Type params) {
+  static const auto cached = S_get(new ExtType_Float(CreateCategory_Float(), Params<0>::Type()));
+  return cached;
+}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+S<TypeValue> Box_Float(PrimFloat value) {
+  return S_get(new ExtValue_Float(CreateType_Float(Params<0>::Type()), value));
+}
diff --git a/base/src/Extension_Int.cpp b/base/src/Extension_Int.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Extension_Int.cpp
@@ -0,0 +1,118 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include <sstream>
+
+#include "category-source.hpp"
+#include "Streamlined_Int.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_AsChar.hpp"
+#include "Category_AsFloat.hpp"
+#include "Category_AsInt.hpp"
+#include "Category_Bool.hpp"
+#include "Category_Char.hpp"
+#include "Category_Default.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Float.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Int.hpp"
+#include "Category_LessThan.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_Int : public Category_Int {
+};
+
+struct ExtType_Int : public Type_Int {
+  inline ExtType_Int(Category_Int& p, Params<0>::Type params) : Type_Int(p, params) {}
+
+  ReturnTuple Call_default(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Int.default")
+    return ReturnTuple(Box_Int(0));
+  }
+
+  ReturnTuple Call_equals(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Int.equals")
+    const PrimInt Var_arg1 = (args.At(0))->AsInt();
+    const PrimInt Var_arg2 = (args.At(1))->AsInt();
+    return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
+  }
+
+  ReturnTuple Call_lessThan(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Int.lessThan")
+    const PrimInt Var_arg1 = (args.At(0))->AsInt();
+    const PrimInt Var_arg2 = (args.At(1))->AsInt();
+    return ReturnTuple(Box_Bool(Var_arg1<Var_arg2));
+  }
+};
+
+struct ExtValue_Int : public Value_Int {
+  inline ExtValue_Int(S<Type_Int> p, PrimInt value) : Value_Int(p), value_(value) {}
+
+  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Int.asBool")
+    return ReturnTuple(Box_Bool(value_ != 0));
+  }
+
+  ReturnTuple Call_asChar(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Int.asChar")
+    return ReturnTuple(Box_Char(value_ % 0xff));
+  }
+
+  ReturnTuple Call_asFloat(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Int.asFloat")
+    return ReturnTuple(Box_Float(value_));
+  }
+
+  ReturnTuple Call_asInt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Int.asInt")
+    return ReturnTuple(Var_self);
+  }
+
+  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("Int.formatted")
+    std::ostringstream output;
+    output << value_;
+    return ReturnTuple(Box_String(output.str()));
+  }
+
+  PrimInt AsInt() const final { return value_; }
+
+  const PrimInt value_;
+};
+
+Category_Int& CreateCategory_Int() {
+  static auto& category = *new ExtCategory_Int();
+  return category;
+}
+S<Type_Int> CreateType_Int(Params<0>::Type params) {
+  static const auto cached = S_get(new ExtType_Int(CreateCategory_Int(), Params<0>::Type()));
+  return cached;
+}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+S<TypeValue> Box_Int(PrimInt value) {
+  return S_get(new ExtValue_Int(CreateType_Int(Params<0>::Type()), value));
+}
diff --git a/base/src/Extension_String.cpp b/base/src/Extension_String.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/Extension_String.cpp
@@ -0,0 +1,204 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "category-source.hpp"
+#include "Streamlined_String.hpp"
+#include "Category_Append.hpp"
+#include "Category_AsBool.hpp"
+#include "Category_Bool.hpp"
+#include "Category_Build.hpp"
+#include "Category_Char.hpp"
+#include "Category_CharBuffer.hpp"
+#include "Category_Container.hpp"
+#include "Category_Default.hpp"
+#include "Category_DefaultOrder.hpp"
+#include "Category_Equals.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_Int.hpp"
+#include "Category_LessThan.hpp"
+#include "Category_Order.hpp"
+#include "Category_ReadAt.hpp"
+#include "Category_String.hpp"
+#include "Category_SubSequence.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+struct ExtCategory_String : public Category_String {
+};
+
+class Value_StringBuilder : public TypeValue {
+ public:
+  std::string CategoryName() const final { return "StringBuilder"; }
+
+  ReturnTuple Dispatch(const S<TypeValue>& self,
+                       const ValueFunction& label,
+                       const ParamTuple& params, const ValueTuple& args) final {
+    if (args.Size() != label.arg_count) {
+      FAIL() << "Wrong number of args";
+    }
+    if (params.Size() != label.param_count){
+      FAIL() << "Wrong number of params";
+    }
+    if (&label == &Function_Append_append) {
+      TRACE_FUNCTION("StringBuilder.append")
+      std::lock_guard<std::mutex> lock(mutex);
+      output_ << args.At(0)->AsString();
+      return ReturnTuple(self);
+    }
+    if (&label == &Function_Build_build) {
+      TRACE_FUNCTION("StringBuilder.build")
+      std::lock_guard<std::mutex> lock(mutex);
+      return ReturnTuple(Box_String(output_.str()));
+    }
+    return TypeValue::Dispatch(self, label, params, args);
+  }
+
+ private:
+  std::mutex mutex;
+  std::ostringstream output_;
+};
+
+struct ExtType_String : public Type_String {
+  inline ExtType_String(Category_String& p, Params<0>::Type params) : Type_String(p, params) {}
+
+  ReturnTuple Call_builder(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.builder")
+    return ReturnTuple(S<TypeValue>(new Value_StringBuilder));
+  }
+
+  ReturnTuple Call_default(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.default")
+    return ReturnTuple(Box_String(""));
+  }
+
+  ReturnTuple Call_equals(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.equals")
+    const S<TypeValue>& Var_arg1 = (args.At(0));
+    const S<TypeValue>& Var_arg2 = (args.At(1));
+    return ReturnTuple(Box_Bool(Var_arg1->AsString()==Var_arg2->AsString()));
+  }
+
+  ReturnTuple Call_fromCharBuffer(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.fromCharBuffer")
+    const S<TypeValue>& Var_arg1 = (args.At(0));
+    return ReturnTuple(Box_String(PrimString(Var_arg1->AsCharBuffer())));
+  }
+
+  ReturnTuple Call_lessThan(const S<TypeInstance>& Param_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.lessThan")
+    const S<TypeValue>& Var_arg1 = (args.At(0));
+    const S<TypeValue>& Var_arg2 = (args.At(1));
+    return ReturnTuple(Box_Bool(Var_arg1->AsString()<Var_arg2->AsString()));
+  }
+};
+
+struct StringOrder : public AnonymousOrder {
+  StringOrder(S<TypeValue> container, const std::string& s)
+    : AnonymousOrder(container, Function_Order_next, Function_Order_get), value(s) {}
+
+  S<TypeValue> Call_next(const S<TypeValue>& self) final {
+    if (index+1 >= value.size()) {
+      return Var_empty;
+    } else {
+      ++index;
+      return self;
+    }
+  }
+
+  S<TypeValue> Call_get(const S<TypeValue>& self) final {
+    if (index >= value.size()) FAIL() << "Iterated past end of String";
+    return Box_Char(value[index]);
+  }
+
+  const std::string& value;
+  int index = 0;
+};
+
+struct ExtValue_String : public Value_String {
+  inline ExtValue_String(S<Type_String> p, const PrimString& value) : Value_String(p), value_(value) {}
+
+  ReturnTuple Call_asBool(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.asBool")
+    return ReturnTuple(Box_Bool(value_.size() != 0));
+  }
+
+  ReturnTuple Call_defaultOrder(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.defaultOrder")
+    if (value_.empty()) {
+      return ReturnTuple(Var_empty);
+    } else {
+      return ReturnTuple(S_get(new StringOrder(Var_self, value_)));
+    }
+  }
+
+  ReturnTuple Call_formatted(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.formatted")
+    return ReturnTuple(Var_self);
+  }
+
+  ReturnTuple Call_readAt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.readAt")
+    const PrimInt Var_arg1 = (args.At(0))->AsInt();
+    if (Var_arg1 < 0 || Var_arg1 >= value_.size()) {
+      FAIL() << "Read position " << Var_arg1 << " is out of bounds";
+    }
+    return ReturnTuple(Box_Char(value_[Var_arg1]));
+  }
+
+  ReturnTuple Call_size(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.size")
+    return ReturnTuple(Box_Int(value_.size()));
+  }
+
+  ReturnTuple Call_subSequence(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
+    TRACE_FUNCTION("String.subSequence")
+    const PrimInt Var_arg1 = (args.At(0))->AsInt();
+    const PrimInt Var_arg2 = (args.At(1))->AsInt();
+    if (Var_arg1 < 0 || Var_arg1 > value_.size()) {
+      FAIL() << "Subsequence position " << Var_arg1 << " is out of bounds";
+    }
+    if (Var_arg2 < 0 || Var_arg1 + Var_arg2 > value_.size()) {
+      FAIL() << "Subsequence size " << Var_arg2 << " is invalid";
+    }
+    return ReturnTuple(Box_String(value_.substr(Var_arg1,Var_arg2)));
+  }
+
+  const PrimString& AsString() const final { return value_; }
+
+  const PrimString value_;
+};
+
+Category_String& CreateCategory_String() {
+  static auto& category = *new ExtCategory_String();
+  return category;
+}
+S<Type_String> CreateType_String(Params<0>::Type params) {
+  static const auto cached = S_get(new ExtType_String(CreateCategory_String(), Params<0>::Type()));
+  return cached;
+}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+S<TypeValue> Box_String(const PrimString& value) {
+  return S_get(new ExtValue_String(CreateType_String(Params<0>::Type()), value));
+}
diff --git a/base/src/category-source.cpp b/base/src/category-source.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/category-source.cpp
@@ -0,0 +1,297 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "category-source.hpp"
+
+#include <algorithm>
+
+#include "logging.hpp"
+
+
+namespace {
+
+struct OptionalEmpty : public TypeValue {
+  ReturnTuple Dispatch(const S<TypeValue>& self,
+                       const ValueFunction& label,
+                       const ParamTuple& params, const ValueTuple& args) final {
+    FAIL() << "Function called on empty value";
+    __builtin_unreachable();
+  }
+
+  std::string CategoryName() const final { return "empty"; }
+
+  bool Present() const final { return false; }
+};
+
+struct Type_Intersect : public TypeInstance {
+  Type_Intersect(L<S<const TypeInstance>> params) : params_(params.begin(), params.end()) {}
+
+  std::string CategoryName() const final { return "(intersection)"; }
+
+  void BuildTypeName(std::ostream& output) const final {
+    if (params_.empty()) {
+      output << "any";
+    } else {
+      output << "[";
+      bool first = true;
+      for (const auto param : params_) {
+        if (!first) output << "&";
+        first = false;
+        param->BuildTypeName(output);
+      }
+      output << "]";
+    }
+  }
+
+  MergeType InstanceMergeType() const final
+  { return MergeType::INTERSECT; }
+
+  std::vector<S<const TypeInstance>> MergedTypes() const final
+  { return params_; }
+
+  const L<S<const TypeInstance>> params_;
+};
+
+struct Type_Union : public TypeInstance {
+  Type_Union(L<S<const TypeInstance>> params) : params_(params.begin(), params.end()) {}
+
+  std::string CategoryName() const final { return "(union)"; }
+
+  void BuildTypeName(std::ostream& output) const final {
+    if (params_.empty()) {
+      output << "all";
+    } else {
+      output << "[";
+      bool first = true;
+      for (const auto param : params_) {
+        if (!first) output << "|";
+        first = false;
+        param->BuildTypeName(output);
+      }
+      output << "]";
+    }
+  }
+
+  MergeType InstanceMergeType() const final
+  { return MergeType::UNION; }
+
+  std::vector<S<const TypeInstance>> MergedTypes() const final
+  { return params_; }
+
+  const L<S<const TypeInstance>> params_;
+};
+
+L<const TypeInstance*> ParamsToKey(const L<S<const TypeInstance>>& params) {
+  L<const TypeInstance*> key;
+  for (const auto& param : params) {
+    key.push_back(param.get());
+  }
+  std::sort(key.begin(), key.end());
+  return key;
+}
+
+}  // namespace
+
+
+S<TypeInstance> Merge_Intersect(L<S<const TypeInstance>> params) {
+  static auto& cache = *new std::map<L<const TypeInstance*>,S<Type_Intersect>>();
+  auto& cached = cache[ParamsToKey(params)];
+  S<Type_Intersect> type = cached;
+  if (!type) { cached = type = S_get(new Type_Intersect(params)); }
+  return type;
+}
+
+S<TypeInstance> Merge_Union(L<S<const TypeInstance>> params) {
+  static auto& cache = *new std::map<L<const TypeInstance*>,S<Type_Union>>();
+  auto& cached = cache[ParamsToKey(params)];
+  S<Type_Union> type = cached;
+  if (!type) { cached = type = S_get(new Type_Union(params)); }
+  return type;
+}
+
+const S<TypeInstance>& GetMerged_Any() {
+  static const auto instance = Merge_Intersect(L_get<S<const TypeInstance>>());
+  return instance;
+}
+
+const S<TypeInstance>& GetMerged_All() {
+  static const auto instance = Merge_Union(L_get<S<const TypeInstance>>());
+  return instance;
+}
+
+
+const S<TypeValue>& Var_empty = *new S<TypeValue>(new OptionalEmpty());
+
+
+ReturnTuple TypeCategory::Dispatch(const CategoryFunction& label,
+                                   const ParamTuple& params, const ValueTuple& args) {
+  FAIL() << CategoryName() << " does not implement " << label;
+  __builtin_unreachable();
+}
+
+ReturnTuple TypeInstance::Dispatch(const S<TypeInstance>& self, const TypeFunction& label,
+                                   const ParamTuple& params, const ValueTuple& args) {
+  FAIL() << CategoryName() << " does not implement " << label;
+  __builtin_unreachable();
+}
+
+ReturnTuple TypeValue::Dispatch(const S<TypeValue>& self, const ValueFunction& label,
+                                const ParamTuple& params, const ValueTuple& args) {
+  FAIL() << CategoryName() << " does not implement " << label;
+  __builtin_unreachable();
+}
+
+bool TypeInstance::CanConvert(const S<const TypeInstance>& x,
+                              const S<const TypeInstance>& y) {
+  // See pairMergeTree for the ordering here.
+  // TODO: Consider using a cache here, since the check could be expensive.
+  if (x.get() == y.get()) {
+    return true;
+  } else if (x->InstanceMergeType() == MergeType::INTERSECT &&
+             y->InstanceMergeType() == MergeType::UNION) {
+    for (const auto& left : x->MergedTypes()) {
+      if (left->InstanceMergeType() == MergeType::SINGLE) {
+        for (const auto& right : y->MergedTypes()) {
+          if (right->InstanceMergeType() == MergeType::SINGLE) {
+            if (TypeInstance::CanConvert(left, right)) {
+              return true;
+            }
+          }
+        }
+      }
+    }
+    for (const auto& left : x->MergedTypes()) {
+      if (TypeInstance::CanConvert(left, y)) {
+        return true;
+      }
+    }
+    for (const auto right : y->MergedTypes()) {
+      if (TypeInstance::CanConvert(x, right)) {
+        return true;
+      }
+    }
+    return false;
+  } else if (y->InstanceMergeType() == MergeType::INTERSECT) {
+    for (const auto& right : y->MergedTypes()) {
+      if (!TypeInstance::CanConvert(x, right)) {
+        return false;
+      }
+    }
+    return true;
+  } else if (x->InstanceMergeType() == MergeType::UNION) {
+    for (const auto& left : x->MergedTypes()) {
+      if (!TypeInstance::CanConvert(left, y)) {
+        return false;
+      }
+    }
+    return true;
+  } else if (x->InstanceMergeType() == MergeType::INTERSECT) {
+    for (const auto& left : x->MergedTypes()) {
+      if (TypeInstance::CanConvert(left, y)) {
+        return true;
+      }
+    }
+    return false;
+  } else if (y->InstanceMergeType() == MergeType::UNION) {
+    for (const auto right : y->MergedTypes()) {
+      if (TypeInstance::CanConvert(x, right)) {
+        return true;
+      }
+    }
+    return false;
+  } else {
+    return y->CanConvertFrom(x);
+  }
+}
+
+bool TypeValue::Present(S<TypeValue> target) {
+  if (target == nullptr) {
+    FAIL() << "Builtin called on null value";
+  }
+  return target->Present();
+}
+
+S<TypeValue> TypeValue::Require(S<TypeValue> target) {
+  if (target == nullptr) {
+    FAIL() << "Builtin called on null value";
+  }
+  if (!target->Present()) {
+    FAIL() << "Cannot require empty value";
+  }
+  return target;
+}
+
+S<TypeValue> TypeValue::Strong(W<TypeValue> target) {
+  const auto strong = target.lock();
+  return strong? strong : Var_empty;
+}
+
+bool TypeValue::AsBool() const {
+  FAIL() << CategoryName() << " is not a Bool value";
+  __builtin_unreachable();
+}
+
+const PrimString& TypeValue::AsString() const {
+  FAIL() << CategoryName() << " is not a String value";
+  __builtin_unreachable();
+}
+
+PrimChar TypeValue::AsChar() const {
+  FAIL() << CategoryName() << " is not a Char value";
+  __builtin_unreachable();
+}
+
+PrimCharBuffer& TypeValue::AsCharBuffer() {
+  FAIL() << CategoryName() << " is not a CharBuffer value";
+  __builtin_unreachable();
+}
+
+PrimInt TypeValue::AsInt() const {
+  FAIL() << CategoryName() << " is not an Int value";
+  __builtin_unreachable();
+}
+
+PrimFloat TypeValue::AsFloat() const {
+  FAIL() << CategoryName() << " is not a Float value";
+  __builtin_unreachable();
+}
+
+bool TypeValue::Present() const {
+  return true;
+}
+
+AnonymousOrder::AnonymousOrder(const S<TypeValue> cont,
+                               const ValueFunction& func_next,
+                               const ValueFunction& func_get)
+  : container(cont), function_next(func_next), function_get(func_get) {}
+
+std::string AnonymousOrder::CategoryName() const { return "AnonymousOrder"; }
+
+ReturnTuple AnonymousOrder::Dispatch(
+  const S<TypeValue>& self, const ValueFunction& label,
+  const ParamTuple& params, const ValueTuple& args) {
+  if (&label == &function_next) {
+    TRACE_FUNCTION("AnonymousOrder.next")
+    return ReturnTuple(Call_next(self));
+  }
+  if (&label == &function_get) {
+    TRACE_FUNCTION("AnonymousOrder.get")
+    return ReturnTuple(Call_get(self));
+  }
+  return TypeValue::Dispatch(self, label, params, args);
+}
diff --git a/base/src/logging.cpp b/base/src/logging.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/logging.cpp
@@ -0,0 +1,234 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2020 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "logging.hpp"
+
+#include <cassert>
+#include <chrono>
+#include <csignal>
+#include <iomanip>
+#include <iostream>
+
+
+LogThenCrash::LogThenCrash(bool fail, const std::string& condition)
+    : fail_(fail), signal_(SIGABRT), condition_(condition) {}
+
+LogThenCrash::LogThenCrash(bool fail, int signal)
+    : fail_(fail), signal_(signal) {}
+
+LogThenCrash::~LogThenCrash() {
+  if (fail_) {
+    std::signal(signal_, SIG_DFL);
+    if (Argv::ArgCount() > 0) {
+      std::cerr << Argv::GetArgAt(0) << ": ";
+    }
+    std::cerr << "Failed condition";
+    if (!condition_.empty()) {
+      std::cerr << " '" << condition_ << "'";
+    }
+    std::cerr << ": " << output_.str() << std::endl;
+    PrintTrace(TraceContext::GetTrace());
+    const TraceList creation_trace = TraceCreation::GetTrace();
+    if (!creation_trace.empty()) {
+      std::cerr << TraceCreation::GetType() << " value originally created at:" << std::endl;
+      PrintTrace(creation_trace);
+    }
+    std::raise(signal_);
+  }
+}
+
+// static
+void LogThenCrash::PrintTrace(const TraceList &call_trace) {
+  for (const auto& trace : call_trace) {
+    const std::string message = trace();
+    if (!message.empty()) {
+      std::cerr << "  " << message << std::endl;
+    }
+  }
+}
+
+// TODO: Should only be available used if POSIX is defined.
+void TraceOnSignal(int signal) {
+  LogThenCrash(true,signal) << "Recieved signal " << signal;
+}
+
+// TODO: Should only be available used if POSIX is defined.
+void SetSignalHandler() {
+#ifdef SIGINT
+  std::signal(SIGINT, &TraceOnSignal);
+#endif
+#ifdef SIGILL
+  std::signal(SIGILL, &TraceOnSignal);
+#endif
+#ifdef SIGABRT
+  std::signal(SIGABRT, &TraceOnSignal);
+#endif
+#ifdef SIGFPE
+  std::signal(SIGFPE, &TraceOnSignal);
+#endif
+#ifdef SIGQUIT
+  std::signal(SIGQUIT, &TraceOnSignal);
+#endif
+#ifdef SIGSEGV
+  std::signal(SIGSEGV, &TraceOnSignal);
+#endif
+#ifdef SIGPIPE
+  std::signal(SIGPIPE, &TraceOnSignal);
+#endif
+#ifdef SIGALRM
+  std::signal(SIGALRM, &TraceOnSignal);
+#endif
+#ifdef SIGTERM
+  std::signal(SIGTERM, &TraceOnSignal);
+#endif
+#ifdef SIGUSR1
+  std::signal(SIGUSR1, &TraceOnSignal);
+#endif
+#ifdef SIGUSR2
+  std::signal(SIGUSR2, &TraceOnSignal);
+#endif
+}
+
+
+TraceList TraceContext::GetTrace() {
+  TraceList trace;
+  const TraceContext* current = GetCurrent();
+  while (current) {
+    current->AppendTrace(trace);
+    current = current->GetNext();
+  }
+  return trace;
+}
+
+
+void SourceContext::SetLocal(const char* at) {
+  at_ = at;
+  LogCalls::MaybeLogCall(name_, at_);
+}
+
+void SourceContext::AppendTrace(TraceList& trace) const {
+  const char* const name = name_;
+  const char* const at   = at_;
+  trace.push_back([name,at]() {
+      std::ostringstream output;
+      if (at == nullptr || at[0] == 0x00) {
+        output << "From " << name;
+      } else {
+        output << "From " << name << " at " << at;
+      }
+      return output.str();
+    });
+}
+
+const TraceContext* SourceContext::GetNext() const {
+  return capture_to_.Previous();
+}
+
+
+void CleanupContext::SetLocal(const char* at) {
+  at_ = at;
+}
+
+void CleanupContext::AppendTrace(TraceList& trace) const {
+  const char* const at = at_;
+  trace.push_back([at]() {
+      std::ostringstream output;
+      if (at == nullptr || at[0] == 0x00) {
+        output << "In cleanup block";
+      } else {
+        output << "In cleanup block at " << at;
+      }
+      return output.str();
+    });
+}
+
+const TraceContext* CleanupContext::GetNext() const {
+  return capture_to_.Previous();
+}
+
+int Argv::ArgCount() {
+  if (GetCurrent()) {
+    return GetCurrent()->GetArgs().size();
+  } else {
+    return 0;
+  }
+}
+
+const std::string& Argv::GetArgAt(int pos) {
+  if (pos < 0 || pos >= ArgCount()) {
+    FAIL() << "Argv index " << pos << " is out of bounds";
+    __builtin_unreachable();
+  } else {
+    return GetCurrent()->GetArgs()[pos];
+  }
+}
+
+const std::vector<std::string>& ProgramArgv::GetArgs() const {
+  return argv_;
+}
+
+std::string FixCsvString(const char* string) {
+  std::string fixed;
+  while (*string) {
+    switch (*string) {
+      case '\\':
+        break;
+      case '"':
+        fixed.push_back('\'');
+        break;
+      default:
+        fixed.push_back(*string);
+        break;
+    }
+    ++string;
+  }
+  return fixed;
+}
+
+unsigned int UniqueId() {
+  const auto time = std::chrono::steady_clock::now().time_since_epoch();
+  return (1000000009 * std::chrono::duration_cast<std::chrono::microseconds>(time).count());
+}
+
+LogCallsToFile::LogCallsToFile(std::string filename)
+  : unique_id_(UniqueId()),
+    filename_(std::move(filename)),
+    log_file_(filename_.empty()?
+                nullptr :
+                new std::fstream(filename_, std::ios::in |
+                                            std::ios::out |
+                                            std::ios::ate |
+                                            std::ios::app)),
+    cross_and_capture_to_(this) {
+  if (log_file_) {
+    if (!*log_file_) {
+      FAIL() << "Failed to open call log " << filename_ << " for writing";
+    }
+  }
+}
+
+void LogCallsToFile::LogCall(const char* name, const char* at) {
+  if (log_file_) {
+    std::lock_guard<std::mutex> lock(mutex_);
+    const auto time = std::chrono::steady_clock::now().time_since_epoch();
+    *log_file_ << std::chrono::duration_cast<std::chrono::microseconds>(time).count() << ","
+               << unique_id_ << ","
+               << "\"" << FixCsvString(name) << "\"" << ","
+               << "\"" << FixCsvString(at) << "\"" << std::endl;
+  }
+}
diff --git a/base/src/thread-crosser.cc b/base/src/thread-crosser.cc
new file mode 100644
--- /dev/null
+++ b/base/src/thread-crosser.cc
@@ -0,0 +1,33 @@
+/* -----------------------------------------------------------------------------
+Copyright 2017 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
+
+#include "thread-crosser.h"
+
+namespace capture_thread {
+
+namespace {
+thread_local ThreadCrosser* current(nullptr);
+}
+
+// static
+ThreadCrosser* ThreadCrosser::GetCurrent() { return current; }
+
+// static
+void ThreadCrosser::SetCurrent(ThreadCrosser* value) { current = value; }
+
+}  // namespace capture_thread
diff --git a/base/src/types.cpp b/base/src/types.cpp
new file mode 100644
--- /dev/null
+++ b/base/src/types.cpp
@@ -0,0 +1,259 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019,2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+#include "types.hpp"
+
+#include <atomic>
+
+#include "logging.hpp"
+
+
+int ArgTuple::Size() const {
+  return size_;
+}
+
+const S<TypeValue>& ArgTuple::At(int pos) const {
+  if (pos < 0 || pos >= size_) {
+    FAIL() << "Bad ArgTuple index";
+  }
+  return *data_[pos];
+}
+
+const S<TypeValue>& ArgTuple::Only() const {
+  if (size_ != 1) {
+    FAIL() << "Bad ArgTuple index";
+  }
+  return *data_[0];
+}
+
+ReturnTuple& ReturnTuple::operator = (ReturnTuple&& other) {
+  if (Size() != other.Size()) {
+    FAIL() << "ReturnTuple size mismatch in assignment: " << Size()
+           << " (expected) " << other.Size() << " (actual)";
+  }
+  for (int i = 0; i < Size(); ++i) {
+    At(i) = std::move(other.At(i));
+  }
+  return *this;
+}
+
+int ReturnTuple::Size() const {
+  return size_;
+}
+
+S<TypeValue>& ReturnTuple::At(int pos) {
+  if (pos < 0 || pos >= size_) {
+    FAIL() << "Bad ReturnTuple index";
+  }
+  return data_[pos];
+}
+
+const S<TypeValue>& ReturnTuple::At(int pos) const {
+  if (pos < 0 || pos >= size_) {
+    FAIL() << "Bad ReturnTuple index";
+  }
+  return data_[pos];
+}
+
+const S<TypeValue>& ReturnTuple::Only() const {
+  if (size_ != 1) {
+    FAIL() << "Bad ReturnTuple index";
+  }
+  return data_[0];
+}
+
+int ParamTuple::Size() const {
+  return size_;
+}
+
+const S<TypeInstance>& ParamTuple::At(int pos) const {
+  if (pos < 0 || pos >= size_) {
+    FAIL() << "Bad ParamTuple index";
+  }
+  return data_[pos];
+}
+
+
+template<class T>
+class PoolCleanup {
+ public:
+  constexpr PoolCleanup() {}
+
+  ~PoolCleanup() {
+    PoolManager<T>::Purge();
+  }
+
+ private:
+  PoolCleanup(const PoolCleanup&) = delete;
+  PoolCleanup operator = (const PoolCleanup&) = delete;
+  PoolCleanup(PoolCleanup&&) = delete;
+  PoolCleanup operator = (PoolCleanup&&) = delete;
+};
+
+
+static void* const pool_busy_flag_ = (void*) ~0x00;
+
+
+static unsigned int return_tuple_pool4_size_ = 0;
+static std::atomic<PoolStorage<S<TypeValue>>*> return_tuple_pool4_{nullptr};
+
+// static
+PoolStorage<S<TypeValue>>* PoolManager<S<TypeValue>>::Take(int size) {
+  if (size == 0) return nullptr;
+  if (size < 4) {
+    size = 4;
+  }
+  if (size == 4) {
+    PoolEntry* storage = nullptr;
+    while ((storage = return_tuple_pool4_.exchange((PoolEntry*) pool_busy_flag_)) == pool_busy_flag_);
+    if (storage == nullptr) {
+      return_tuple_pool4_.exchange(nullptr);
+    } else {
+      --return_tuple_pool4_size_;
+      return_tuple_pool4_.exchange(storage->next);
+      storage->next = nullptr;
+      new (storage->data()) Managed[storage->size];
+      return storage;
+    }
+  }
+  PoolEntry* const storage = new (new unsigned char[sizeof(PoolEntry) + size*sizeof(Managed)]) PoolEntry(size, nullptr);
+  new (storage->data()) Managed[size];
+  return storage;
+}
+
+// static
+void PoolManager<S<TypeValue>>::Return(PoolEntry* storage) {
+  if (!storage) return;
+  for (int i = 0; i < storage->size; ++i) {
+    storage->data()[i].~Managed();
+  }
+  if (storage->size == 4) {
+    PoolEntry* head = nullptr;
+    while ((head = return_tuple_pool4_.exchange((PoolEntry*) pool_busy_flag_)) == pool_busy_flag_);
+    if (return_tuple_pool4_size_ < pool_limit_) {
+      ++return_tuple_pool4_size_;
+      storage->next = head;
+      return_tuple_pool4_.exchange(storage);
+      return;
+    } else {
+      return_tuple_pool4_.exchange(head);
+    }
+  }
+  storage->~PoolEntry();
+  delete[] (unsigned char*) storage;
+}
+
+
+static unsigned int arg_tuple_pool4_size_ = 0;
+static std::atomic<PoolStorage<const S<TypeValue>*>*> arg_tuple_pool4_{nullptr};
+
+// static
+PoolStorage<const S<TypeValue>*>* PoolManager<const S<TypeValue>*>::Take(int size) {
+  if (size == 0) return nullptr;
+  if (size < 4) {
+    size = 4;
+  }
+  if (size == 4) {
+    PoolEntry* storage = nullptr;
+    while ((storage = arg_tuple_pool4_.exchange((PoolEntry*) pool_busy_flag_)) == pool_busy_flag_);
+    if (storage == nullptr) {
+      arg_tuple_pool4_.exchange(nullptr);
+    } else {
+      --arg_tuple_pool4_size_;
+      arg_tuple_pool4_.exchange(storage->next);
+      storage->next = nullptr;
+      new (storage->data()) Managed[storage->size];
+      return storage;
+    }
+  }
+  PoolEntry* const storage = new (new unsigned char[sizeof(PoolEntry) + size*sizeof(Managed)]) PoolEntry(size, nullptr);
+  new (storage->data()) Managed[size];
+  return storage;
+}
+
+// static
+void PoolManager<const S<TypeValue>*>::Return(PoolEntry* storage) {
+  if (!storage) return;
+  for (int i = 0; i < storage->size; ++i) {
+    storage->data()[i].~Managed();
+  }
+  if (storage->size == 4) {
+    PoolEntry* head = nullptr;
+    while ((head = arg_tuple_pool4_.exchange((PoolEntry*) pool_busy_flag_)) == pool_busy_flag_);
+    if (arg_tuple_pool4_size_ < pool_limit_) {
+      ++arg_tuple_pool4_size_;
+      storage->next = head;
+      arg_tuple_pool4_.exchange(storage);
+      return;
+    } else {
+      arg_tuple_pool4_.exchange(head);
+    }
+  }
+  storage->~PoolEntry();
+  delete[] (unsigned char*) storage;
+}
+
+
+static unsigned int param_tuple_pool4_size_ = 0;
+static std::atomic<PoolStorage<S<TypeInstance>>*> param_tuple_pool4_{nullptr};
+
+// static
+PoolStorage<S<TypeInstance>>* PoolManager<S<TypeInstance>>::Take(int size) {
+  if (size == 0) return nullptr;
+  if (size < 4) {
+    size = 4;
+  }
+  if (size == 4) {
+    PoolEntry* storage = nullptr;
+    while ((storage = param_tuple_pool4_.exchange((PoolEntry*) pool_busy_flag_)) == pool_busy_flag_);
+    if (storage == nullptr) {
+      param_tuple_pool4_.exchange(nullptr);
+    } else {
+      --param_tuple_pool4_size_;
+      param_tuple_pool4_.exchange(storage->next);
+      storage->next = nullptr;
+      new (storage->data()) Managed[storage->size];
+      return storage;
+    }
+  }
+  PoolEntry* const storage = new (new unsigned char[sizeof(PoolEntry) + size*sizeof(Managed)]) PoolEntry(size, nullptr);
+  new (storage->data()) Managed[size];
+  return storage;
+}
+
+// static
+void PoolManager<S<TypeInstance>>::Return(PoolEntry* storage) {
+  if (!storage) return;
+  for (int i = 0; i < storage->size; ++i) {
+    storage->data()[i].~Managed();
+  }
+  if (storage->size == 4) {
+    PoolEntry* head = nullptr;
+    while ((head = param_tuple_pool4_.exchange((PoolEntry*) pool_busy_flag_)) == pool_busy_flag_);
+    if (param_tuple_pool4_size_ < pool_limit_) {
+      ++param_tuple_pool4_size_;
+      storage->next = head;
+      param_tuple_pool4_.exchange(storage);
+      return;
+    } else {
+      param_tuple_pool4_.exchange(head);
+    }
+  }
+  storage->~PoolEntry();
+  delete[] (unsigned char*) storage;
+}
diff --git a/base/thread-crosser.cc b/base/thread-crosser.cc
deleted file mode 100644
--- a/base/thread-crosser.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2017 Google Inc.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com] [kevinbarry@google.com]
-
-#include "thread-crosser.h"
-
-namespace capture_thread {
-
-namespace {
-thread_local ThreadCrosser* current(nullptr);
-}
-
-// static
-ThreadCrosser* ThreadCrosser::GetCurrent() { return current; }
-
-// static
-void ThreadCrosser::SetCurrent(ThreadCrosser* value) { current = value; }
-
-}  // namespace capture_thread
diff --git a/base/types.cpp b/base/types.cpp
deleted file mode 100644
--- a/base/types.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2019 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-#include "types.hpp"
-
-#include "logging.hpp"
-
-
-int ArgTuple::Size() const {
-  return args_.size();
-}
-
-const S<TypeValue>& ArgTuple::At(int pos) const {
-  if (pos < 0 || pos >= args_.size()) {
-    FAIL() << "Bad ArgTuple index";
-  }
-  return *args_[pos];
-}
-
-const S<TypeValue>& ArgTuple::Only() const {
-  if (args_.size() != 1) {
-    FAIL() << "Bad ArgTuple index";
-  }
-  return *args_[0];
-}
-
-ReturnTuple& ReturnTuple::operator =(ReturnTuple &&other) {
-  if (Size() != other.Size()) {
-    FAIL() << "ReturnTuple size mismatch in assignment: " << Size()
-           << " (expected) " << other.Size() << " (actual)";
-  }
-  for (int i = 0; i < Size(); ++i) {
-    At(i) = std::move(other.At(i));
-  }
-  return *this;
-}
-
-int ReturnTuple::Size() const {
-  return returns_.size();
-}
-
-S<TypeValue>& ReturnTuple::At(int pos) {
-  if (pos < 0 || pos >= returns_.size()) {
-    FAIL() << "Bad ReturnTuple index";
-  }
-  return returns_[pos];
-}
-
-const S<TypeValue>& ReturnTuple::At(int pos) const {
-  if (pos < 0 || pos >= returns_.size()) {
-    FAIL() << "Bad ReturnTuple index";
-  }
-  return returns_[pos];
-}
-
-const S<TypeValue>& ReturnTuple::Only() const {
-  if (returns_.size() != 1) {
-    FAIL() << "Bad ReturnTuple index";
-  }
-  return returns_[0];
-}
-
-int ParamTuple::Size() const {
-  return params_.size();
-}
-
-const S<TypeInstance>& ParamTuple::At(int pos) const {
-  if (pos < 0 || pos >= params_.size()) {
-    FAIL() << "Bad ParamTuple index";
-  }
-  return params_[pos];
-}
diff --git a/base/types.hpp b/base/types.hpp
--- a/base/types.hpp
+++ b/base/types.hpp
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2019-2020 Kevin P. Barry
+Copyright 2019-2021 Kevin P. Barry
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include "logging.hpp"
+#include "pooled.hpp"
 
 
 #define ALWAYS_PERMANENT(type) \
@@ -34,9 +35,12 @@
   type& operator =(const type&) = delete; \
   type& operator =(type&&) = delete;
 
+using CollectionType = int;
+
 using PrimInt = std::int64_t;
 using PrimString = std::string;
 using PrimChar = char;
+using PrimCharBuffer = std::string;
 using PrimFloat = double;
 
 template<int S>
@@ -172,16 +176,31 @@
   void* operator new(std::size_t size) = delete;
 };
 
+template<>
+class PoolManager<S<TypeValue>> {
+  using Managed = S<TypeValue>;
+  using PoolEntry = PoolStorage<Managed>;
+
+  template<class> friend class PoolArray;
+
+  static PoolEntry* Take(int size);
+  static void Return(PoolEntry* storage);
+
+  static constexpr unsigned int pool_limit_ = 256;
+};
+
 class ReturnTuple : public ValueTuple {
  public:
-  ReturnTuple(int size) : returns_(size) {}
-
-  ReturnTuple(ReturnTuple&&) = default;
-  ReturnTuple& operator =(ReturnTuple&&);
+  ReturnTuple(int size) : size_(size), data_(size_) {}
 
   template<class...Ts>
-  explicit ReturnTuple(Ts... returns) : returns_{std::move(returns)...} {}
+  explicit ReturnTuple(Ts... returns) : size_(sizeof...(Ts)), data_(size_) {
+    data_.Init(std::move(returns)...);
+  }
 
+  ReturnTuple(ReturnTuple&&) = default;
+  ReturnTuple& operator = (ReturnTuple&&);
+
   int Size() const final;
   S<TypeValue>& At(int pos);
   const S<TypeValue>& At(int pos) const final;
@@ -190,14 +209,31 @@
  private:
   ReturnTuple(const ReturnTuple&) = delete;
   ReturnTuple& operator =(const ReturnTuple&) = delete;
+  void* operator new(std::size_t size) = delete;
 
-  std::vector<S<TypeValue>> returns_;
+  int size_;
+  PoolArray<S<TypeValue>> data_;
 };
 
+template<>
+class PoolManager<const S<TypeValue>*> {
+  using Managed = const S<TypeValue>*;
+  using PoolEntry = PoolStorage<Managed>;
+
+  template<class> friend class PoolArray;
+
+  static PoolEntry* Take(int size);
+  static void Return(PoolEntry* storage);
+
+  static constexpr unsigned int pool_limit_ = 256;
+};
+
 class ArgTuple : public ValueTuple {
  public:
   template<class...Ts>
-  explicit ArgTuple(const Ts&... args) : args_{&args...} {}
+  explicit ArgTuple(const Ts&... args) : size_(sizeof...(Ts)), data_(size_) {
+    data_.Init(&args...);
+  }
 
   int Size() const final;
   const S<TypeValue>& At(int pos) const final;
@@ -206,28 +242,47 @@
  private:
   ArgTuple(const ArgTuple&) = delete;
   ArgTuple(ArgTuple&&) = delete;
-  ArgTuple& operator =(const ArgTuple&) = delete;
-  ArgTuple& operator =(ArgTuple&&) =  delete;
+  ArgTuple& operator = (const ArgTuple&) = delete;
+  ArgTuple& operator = (ArgTuple&&) =  delete;
+  void* operator new(std::size_t size) = delete;
 
-  std::vector<const S<TypeValue>*> args_;
+  int size_;
+  PoolArray<const S<TypeValue>*> data_;
 };
 
+template<>
+class PoolManager<S<TypeInstance>> {
+  using Managed = S<TypeInstance>;
+  using PoolEntry = PoolStorage<Managed>;
+
+  template<class> friend class PoolArray;
+
+  static PoolEntry* Take(int size);
+  static void Return(PoolEntry* storage);
+
+  static constexpr unsigned int pool_limit_ = 256;
+};
+
 class ParamTuple {
  public:
-  ParamTuple(ParamTuple&& other) : params_(std::move(other.params_)) {}
-
   template<class...Ts>
-  explicit ParamTuple(const Ts&... args) : params_{args...} {}
+  explicit ParamTuple(const Ts&... params) : size_(sizeof...(Ts)), data_(size_) {
+    data_.Init(std::move(params)...);
+  }
 
+  ParamTuple(ParamTuple&& other) = default;
+
   int Size() const;
   const S<TypeInstance>& At(int pos) const;
 
  private:
   ParamTuple(const ParamTuple&) = delete;
-  ParamTuple& operator =(const ParamTuple&) = delete;
+  ParamTuple& operator = (const ParamTuple&) = delete;
+  ParamTuple& operator = (ParamTuple&&) = delete;
   void* operator new(std::size_t size) = delete;
 
-  std::vector<S<TypeInstance>> params_;
+  int size_;
+  PoolArray<S<TypeInstance>> data_;
 };
 
 inline ReturnTuple FailWhenNull(ReturnTuple values) {
diff --git a/example/parser/parse-text.0rp b/example/parser/parse-text.0rp
--- a/example/parser/parse-text.0rp
+++ b/example/parser/parse-text.0rp
@@ -1,23 +1,19 @@
-/* Parses a fixed string.
- */
+// Parses a fixed string.
 concrete StringParser {
   @type create (String) -> (Parser<String>)
 }
 
-/* Parses a string containing a limited set of characters.
- */
+// Parses a string containing a limited set of characters.
 concrete SequenceOfParser {
   @type create (String, Int /*min*/, Int /*max*/) -> (Parser<String>)
 }
 
-/* Parses a fixed character.
- */
+// Parses a fixed character.
 concrete CharParser {
   @type create (Char) -> (Parser<Char>)
 }
 
-/* Parser combinators.
- */
+// Parser combinators.
 concrete Parse {
   @type const<#x> (#x)                     -> (Parser<#x>)
   @type error     (Formatted)              -> (Parser<all>)
diff --git a/example/parser/parser.0rp b/example/parser/parser.0rp
--- a/example/parser/parser.0rp
+++ b/example/parser/parser.0rp
@@ -1,29 +1,26 @@
-/* Manages state for parsing operations outside of a Parser.
- *
- * The state is immutable; all update operations return a new state.
- *
- * Since #x is covariant, any ParseState can convert to ParseState<any>, and
- * ParseState<all> can convert to all other ParseState.
- */
+// Manages state for parsing operations outside of a Parser.
+//
+// The state is immutable; all update operations return a new state.
+//
+// Since #x is covariant, any ParseState can convert to ParseState<any>, and
+// ParseState<all> can convert to all other ParseState.
 concrete ParseState<|#x> {
   // Consumes all input and returns the result.
   @category consumeAll<#y> (Parser<#y>,String) -> (ErrorOr<#y>)
 }
 
-/* A self-contained parser operation.
- *
- * Since #x is covariant, any Parser can convert to Parser<any>, and Parser<all>
- * can convert to all other Parser.
- */
+// A self-contained parser operation.
+//
+// Since #x is covariant, any Parser can convert to Parser<any>, and Parser<all>
+// can convert to all other Parser.
 @value interface Parser<|#x> {
   run (ParseContext<any>) -> (ParseState<#x>)
 }
 
-/* Parser context available when running a Parser.
- *
- * Since #x is covariant, any ParseContext can convert to ParseContext<any>, and
- * ParseContext<all> can convert to all other ParseContext.
- */
+// Parser context available when running a Parser.
+//
+// Since #x is covariant, any ParseContext can convert to ParseContext<any>, and
+// ParseContext<all> can convert to all other ParseContext.
 @value interface ParseContext<|#x> {
   // Continue computation.
   run<#y>       (Parser<#y>) -> (ParseContext<#y>)
diff --git a/example/parser/test-data.0rp b/example/parser/test-data.0rp
--- a/example/parser/test-data.0rp
+++ b/example/parser/test-data.0rp
@@ -1,7 +1,6 @@
 $TestsOnly$
 
-/* A contrived object for test data.
- */
+// A contrived object for test data.
 concrete TestData {
   @type create (String,String,Bool) -> (TestData)
 
@@ -11,8 +10,7 @@
 }
 
 
-/* Parses the test-data.txt file using a one-off format.
- */
+// Parses the test-data.txt file using a one-off format.
 concrete TestDataParser {
   @type create () -> (Parser<TestData>)
 }
diff --git a/example/primes/flag.0rx b/example/primes/flag.0rx
--- a/example/primes/flag.0rx
+++ b/example/primes/flag.0rx
@@ -12,8 +12,10 @@
       MutexLock lock <- MutexLock.lock(cond)
     } cleanup {
       \ lock.freeResource()
-    } in enabled <- true
-    \ cond.resumeAll()
+    } in {
+      enabled <- true
+      \ cond.resumeAll()
+    }
     return self
   }
 
@@ -39,8 +41,10 @@
       MutexLock lock <- MutexLock.lock(cond)
     } cleanup {
       \ lock.freeResource()
-    } in canceled <- true
-    \ cond.resumeAll()
+    } in {
+      canceled <- true
+      \ cond.resumeAll()
+    }
     return self
   }
 
diff --git a/lib/container/auto-tree.0rp b/lib/container/auto-tree.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/auto-tree.0rp
@@ -0,0 +1,141 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+// Provides basic balanced BST functionality to custom containers.
+//
+// Params:
+// - #n: The type of node to be managed.
+// - #k: The key type used by #n.
+// - #v: The value type used by #n.
+// - #r: A read-only subset of the functionality of #n.
+//
+// Notes:
+// - This is not meant for direct use: It is only exposed in this library to
+//   allow other container authors to extend BST functionality by augmenting the
+//   data stored along with each node. This allows the augmented data to rely on
+//   where the node is within the tree.
+// - #r should not expose any functionality of #n that can alter the integrity
+//   of the tree. It is provided as a filter for the return of getRoot() so that
+//   a container can implement custom functionality using the tree's structure
+//   without risking modification of the tree.
+concrete AutoBinaryTree<|#n,#k,#v|#r> {
+  refines Container
+  #n defines  KVFactory<#k,#v>
+  #n requires BalancedTreeNode<#n,#k,#v>
+  #k defines  LessThan<#k>
+  #r allows   #n
+
+  // Create a new tree.
+  @type new () -> (#self)
+
+  // Get the root of the tree.
+  @value getRoot () -> (optional #r)
+
+  // Get the value associated with the key.
+  @value get (#k) -> (optional #v)
+
+  // Set the value associated with the key.
+  //
+  // Notes:
+  // - updateNode() will be called on every affected node, starting with the
+  //   deepest node. It might be called an arbitrary number of times per node.
+  @value set (#k,#v) -> ()
+
+  // Remove the node associated with the key.
+  //
+  // Notes:
+  // - updateNode() will be called on every affected node, starting with the
+  //   deepest node. It might be called an arbitrary number of times per node.
+  @value remove (#k) -> ()
+}
+
+// An interface for reading the state of a BST node.
+//
+// Notes:
+// - This is intended for internal use in BST-based categories.
+@value interface BinaryTreeNode<|#k,#v> {
+  getLower  () -> (optional #self)
+  getHigher () -> (optional #self)
+  getKey    () -> (#k)
+  getValue  () -> (#v)
+  getHeight () -> (Int)
+}
+
+// An interface for managing the state of a BST node.
+//
+// Notes:
+// - This is intended for internal use in BST-based categories.
+@value interface BalancedTreeNode<#n|#k,#v|> {
+  refines BinaryTreeNode<#k,#v>
+
+  // Set the lower child of the node.
+  //
+  // Notes:
+  // - The key for the child is strictly less-than this node's key.
+  // - updateNode() will be called separately to update the node's state.
+  setLower (optional #n) -> ()
+
+  // Set the higher child of the node.
+  //
+  // Notes:
+  // - The key for the child is strictly greater-than this node's key.
+  // - updateNode() will be called separately to update the node's state.
+  setHigher (optional #n) -> ()
+
+  // Set the value associated with the node.
+  setValue (#v) -> ()
+
+  // Update the state of the node, given its position in the tree.
+  //
+  // Notes:
+  // - This should update the height of the subtree that starts with this node;
+  //   otherwise, the tree might not remain balanced.
+  // - Also update any other node state that depends on the node's position
+  //   within the tree.
+  // - This can be called an arbitrary number of times at any time; therefore,
+  //   it must always be idempotent.
+  updateNode () -> ()
+}
+
+// Provides forward iteration of the nodes in a BST.
+//
+// Notes:
+// - This is intended for internal use in BST-based categories.
+concrete ForwardTreeOrder<|#k,#v> {
+  refines Order<KeyValue<#k,#v>>
+
+  @category create<#k,#v> (optional BinaryTreeNode<#k,#v>) -> (optional ForwardTreeOrder<#k,#v>)
+
+  @category seek<#k,#v>
+    #k defines LessThan<#k>
+  (#k,optional BinaryTreeNode<#k,#v>) -> (optional ForwardTreeOrder<#k,#v>)
+}
+
+// Provides reverse iteration of the nodes in a BST.
+//
+// Notes:
+// - This is intended for internal use in BST-based categories.
+concrete ReverseTreeOrder<|#k,#v> {
+  refines Order<KeyValue<#k,#v>>
+
+  @category create<#k,#v> (optional BinaryTreeNode<#k,#v>) -> (optional ReverseTreeOrder<#k,#v>)
+
+  @category seek<#k,#v>
+    #k defines LessThan<#k>
+  (#k,optional BinaryTreeNode<#k,#v>) -> (optional ReverseTreeOrder<#k,#v>)
+}
diff --git a/lib/container/auto-tree.0rx b/lib/container/auto-tree.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/auto-tree.0rx
@@ -0,0 +1,350 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+define AutoBinaryTree {
+  @value Int treeSize
+  @value optional #n root
+
+  new () {
+    return #self{ 0, empty }
+  }
+
+  size () {
+    return treeSize
+  }
+
+  getRoot () {
+    return root
+  }
+
+  get (k) {
+    return find(root,k)
+  }
+
+  set (k,v) {
+    root, Bool added <- insert(root,k,v)
+    if (added) {
+      treeSize <- treeSize+1
+    }
+  }
+
+  remove (k) {
+    root, Bool removed <- delete(root,k)
+    if (removed) {
+      treeSize <- treeSize-1
+    }
+  }
+
+
+  @type insert (optional #n,#k,#v) -> (optional #n,Bool)
+  insert (node,k,v) (newRoot,added) {
+    if (!present(node)) {
+      return #n.newNode(k,v), true
+    }
+    #n node2 <- require(node)
+    if (k `#k.lessThan` node2.getKey()) {
+      newRoot, added <- insert(node2.getLower(),k,v)
+      \ node2.setLower(newRoot)
+      newRoot <- rebalance(node2)
+    } elif (node2.getKey() `#k.lessThan` k) {
+      newRoot, added <- insert(node2.getHigher(),k,v)
+      \ node2.setHigher(newRoot)
+      newRoot <- rebalance(node2)
+    } else {
+      \ node2.setValue(v)
+      \ node2.updateNode()
+      return node2, false
+    }
+  }
+
+  @type delete (optional #n,#k) -> (optional #n,Bool)
+  delete (node,k) (newRoot,removed) {
+    if (!present(node)) {
+      return empty, false
+    }
+    #n node2 <- require(node)
+    if (k `#k.lessThan` node2.getKey()) {
+      newRoot, removed <- delete(node2.getLower(),k)
+      \ node2.setLower(newRoot)
+      newRoot <- rebalance(node2)
+    } elif (node2.getKey() `#k.lessThan` k) {
+      newRoot, removed <- delete(node2.getHigher(),k)
+      \ node2.setHigher(newRoot)
+      newRoot <- rebalance(node2)
+    } else {
+      return rebalance(removeNode(node2)), true
+    }
+  }
+
+  @type find (optional #n,#k) -> (optional #v)
+  find (node,k) {
+    if (present(node)) {
+      scoped {
+        #n node2 <- require(node)
+      } in if (k `#k.lessThan` node2.getKey()) {
+        return find(node2.getLower(),k)
+      } elif (node2.getKey() `#k.lessThan` k) {
+        return find(node2.getHigher(),k)
+      } else {
+        return node2.getValue()
+      }
+    } else {
+      return empty
+    }
+  }
+
+  @type getBalance (optional #n) -> (Int)
+  getBalance (node) (balance) {
+    balance <- 0
+    if (present(node)) {
+      #n node2 <- require(node)
+      if (present(node2.getLower())) {
+        balance <- balance-require(node2.getLower()).getHeight()
+      }
+      if (present(node2.getHigher())) {
+        balance <- balance+require(node2.getHigher()).getHeight()
+      }
+    }
+  }
+
+  @type rebalance (optional #n) -> (optional #n)
+  rebalance (node) {
+    if (!present(node)) {
+      return empty
+    }
+    #n node2 <- require(node)
+    \ node2.updateNode()
+    scoped {
+      Int balance <- getBalance(node2)
+    } in if (balance > 1) {
+      return pivotLower(node2)
+    } elif (balance < -1) {
+      return pivotHigher(node2)
+    } else {
+      return node2
+    }
+  }
+
+  @type pivotHigher (#n) -> (#n)
+  pivotHigher (node) (newNode) {
+    if (getBalance(node.getLower()) > 0) {
+      \ node.setLower(pivotLower(require(node.getLower())))
+    }
+    newNode <- require(node.getLower())
+    \ node.setLower(newNode.getHigher())
+    \ node.updateNode()
+    \ newNode.setHigher(node)
+    \ newNode.updateNode()
+  }
+
+  @type pivotLower (#n) -> (#n)
+  pivotLower (node) (newNode) {
+    if (getBalance(node.getHigher()) < 0) {
+      \ node.setHigher(pivotHigher(require(node.getHigher())))
+    }
+    newNode <- require(node.getHigher())
+    \ node.setHigher(newNode.getLower())
+    \ node.updateNode()
+    \ newNode.setLower(node)
+    \ newNode.updateNode()
+  }
+
+  @type removeNode (#n) -> (optional #n)
+  removeNode (node) (newNode) {
+    if (getBalance(node) < 0) {
+      optional #n temp, newNode <- removeHighest(node.getLower())
+      \ node.setLower(temp)
+    } else {
+      optional #n temp, newNode <- removeLowest(node.getHigher())
+      \ node.setHigher(temp)
+    }
+    if (present(newNode)) {
+      \ swapChildren(node,require(newNode))
+      \ require(newNode).updateNode()
+    }
+  }
+
+  @type removeHighest (optional #n) -> (optional #n,optional #n)
+  removeHighest (node) (newNode,removed) {
+    if (!present(node)) {
+      return empty, empty
+    }
+    #n node2 <- require(node)
+    if (present(node2.getHigher())) {
+      optional #n temp, removed <- removeHighest(node2.getHigher())
+      \ node2.setHigher(temp)
+      newNode <- rebalance(node2)
+    } else {
+      newNode <- node2.getLower()
+      \ node2.setLower(empty)
+      removed <- node
+    }
+  }
+
+  @type removeLowest (optional #n) -> (optional #n,optional #n)
+  removeLowest (node) (newNode,removed) {
+    if (!present(node)) {
+      return empty, empty
+    }
+    #n node2 <- require(node)
+    if (present(node2.getLower())) {
+      optional #n temp, removed <- removeLowest(node2.getLower())
+      \ node2.setLower(temp)
+      newNode <- rebalance(node2)
+    } else {
+      newNode <- node2.getHigher()
+      \ node2.setHigher(empty)
+      removed <- node
+    }
+  }
+
+  @type swapChildren (#n,#n) -> ()
+  swapChildren (l,r) {
+    scoped {
+      optional #n temp <- l.getLower()
+      \ l.setLower(r.getLower())
+    } in \ r.setLower(temp)
+    scoped {
+      optional #n temp <- l.getHigher()
+      \ l.setHigher(r.getHigher())
+    } in \ r.setHigher(temp)
+  }
+}
+
+concrete TreeKeyValue<|#k,#v> {
+  refines KeyValue<#k,#v>
+
+  @category create<#k,#v> (#k,#v) -> (TreeKeyValue<#k,#v>)
+}
+
+define TreeKeyValue {
+  $ReadOnly[key,value]$
+
+  @value #k key
+  @value #v value
+
+  create (key,value) {
+    return TreeKeyValue<#k,#v>{ key, value }
+  }
+
+  getKey () {
+    return key
+  }
+
+  getValue () {
+    return value
+  }
+}
+
+define ForwardTreeOrder {
+  $ReadOnly[node,prev]$
+
+  @value BinaryTreeNode<#k,#v> node
+  @value optional ForwardTreeOrder<#k,#v> prev
+
+  create (node) (current) {
+    optional BinaryTreeNode<#k,#v> node2 <- node
+    current <- empty
+    while (present(node2)) {
+      current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
+      node2 <- require(node2).getLower()
+    }
+  }
+
+  seek (key,node) (current) {
+    optional BinaryTreeNode<#k,#v> node2 <- node
+    current <- empty
+    while (present(node2)) {
+      if (require(node2).getKey() `#k.lessThan` key) {
+        // Skip node2 in the traversal, since it's before key.
+        node2 <- require(node2).getHigher()
+      } elif (key `#k.lessThan` require(node2).getKey()) {
+        current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
+        node2 <- require(node2).getLower()
+      } else {
+        current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
+        break
+      }
+    }
+  }
+
+  next () (current) {
+    // Algorithm:
+    // 1. Pop self from the stack.
+    // 2. Traverse lower to the bottom starting from the higher child of self.
+    optional BinaryTreeNode<#k,#v> node2 <- node.getHigher()
+    current <- prev
+    while (present(node2)) {
+      current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
+      node2 <- require(node2).getLower()
+    }
+  }
+
+  get () {
+    return TreeKeyValue:create<?,?>(node.getKey(),node.getValue())
+  }
+}
+
+define ReverseTreeOrder {
+  $ReadOnly[node,prev]$
+
+  @value BinaryTreeNode<#k,#v> node
+  @value optional ReverseTreeOrder<#k,#v> prev
+
+  create (node) (current) {
+    optional BinaryTreeNode<#k,#v> node2 <- node
+    current <- empty
+    while (present(node2)) {
+      current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
+      node2 <- require(node2).getHigher()
+    }
+  }
+
+  seek (key,node) (current) {
+    optional BinaryTreeNode<#k,#v> node2 <- node
+    current <- empty
+    while (present(node2)) {
+      if (require(node2).getKey() `#k.lessThan` key) {
+        current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
+        node2 <- require(node2).getHigher()
+      } elif (key `#k.lessThan` require(node2).getKey()) {
+        // Skip node2 in the traversal, since it's after key.
+        node2 <- require(node2).getLower()
+      } else {
+        current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
+        break
+      }
+    }
+  }
+
+  next () (current) {
+    // Algorithm:
+    // 1. Pop self from the stack.
+    // 2. Traverse higher to the bottom starting from the lower child of self.
+    optional BinaryTreeNode<#k,#v> node2 <- node.getLower()
+    current <- prev
+    while (present(node2)) {
+      current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
+      node2 <- require(node2).getHigher()
+    }
+  }
+
+  get () {
+    return TreeKeyValue:create<?,?>(node.getKey(),node.getValue())
+  }
+}
diff --git a/lib/container/helpers.0rp b/lib/container/helpers.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/helpers.0rp
@@ -0,0 +1,88 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+// Helpers to extend functionality on keys and values to KeyValue.
+concrete KeyValueH {
+  // Compare for lessThan.
+  //
+  // Params:
+  // - #k: Element type of the key.
+  // - #v: Element type of the value.
+  //
+  // Notes:
+  // - The key comparison takes precedence over the value comparison.
+  //
+  // Example:
+  //
+  //   Bool lt <- x `KeyValueH:lessThan<?,?>` y
+  @category lessThan<#k,#v>
+    #k defines LessThan<#k>
+    #v defines LessThan<#v>
+  (KeyValue<#k,#v>,KeyValue<#k,#v>) -> (Bool)
+
+  // The same as lessThan, but with custom comparators.
+  //
+  // Params:
+  // - #k: Element type of the key.
+  // - #v: Element type of the value.
+  // - #kk: Comparator type providing the LessThan<#k> comparison.
+  // - #vv: Comparator type providing the LessThan<#v> comparison.
+  //
+  // Notes:
+  // - The key comparison takes precedence over the value comparison.
+  //
+  // Example:
+  //
+  //   // Ignore the value and compare Int keys.
+  //   Bool lt <- x `KeyValueH:lessThanWith<?,?,Int,AlwaysEqual>` y
+  @category lessThanWith<#k,#v,#kk,#vv>
+    #kk defines LessThan<#k>
+    #vv defines LessThan<#v>
+  (KeyValue<#k,#v>,KeyValue<#k,#v>) -> (Bool)
+
+  // Compare for equals.
+  //
+  // Params:
+  // - #k: Element type of the key.
+  // - #v: Element type of the value.
+  //
+  // Example:
+  //
+  //   Bool eq <- x `KeyValueH:equals<?,?>` y
+  @category equals<#k,#v>
+    #k defines Equals<#k>
+    #v defines Equals<#v>
+  (KeyValue<#k,#v>,KeyValue<#k,#v>) -> (Bool)
+
+  // The same as equals, but with custom comparators.
+  //
+  // Params:
+  // - #k: Element type of the key.
+  // - #v: Element type of the value.
+  // - #kk: Comparator type providing the Equals<#k> comparison.
+  // - #vv: Comparator type providing the Equals<#v> comparison.
+  //
+  // Example:
+  //
+  //   // Ignore the key and compare Int values.
+  //   Bool eq <- x `KeyValueH:equalsWith<?,?,AlwaysEqual,Int>` y
+  @category equalsWith<#k,#v,#kk,#vv>
+    #kk defines Equals<#k>
+    #vv defines Equals<#v>
+  (KeyValue<#k,#v>,KeyValue<#k,#v>) -> (Bool)
+}
diff --git a/lib/container/helpers.0rt b/lib/container/helpers.0rt
new file mode 100644
--- /dev/null
+++ b/lib/container/helpers.0rt
@@ -0,0 +1,107 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+testcase "comparator helper tests" {
+  success
+}
+
+unittest keyValueHLessThan {
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:lessThan<?,?>` KV.new(1,"a"),false)
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:lessThan<?,?>` KV.new(1,"b"),true)
+  \ Testing.checkEquals<?>(KV.new(2,"a") `KeyValueH:lessThan<?,?>` KV.new(1,"b"),false)
+  \ Testing.checkEquals<?>(KV.new(1,"b") `KeyValueH:lessThan<?,?>` KV.new(2,"a"),true)
+}
+
+unittest keyValueHLessThanWith {
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:lessThanWith<?,?,Int,AlwaysEqual>` KV.new(1,"a"),false)
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:lessThanWith<?,?,Int,AlwaysEqual>` KV.new(1,"b"),false)
+  \ Testing.checkEquals<?>(KV.new(2,"a") `KeyValueH:lessThanWith<?,?,Int,AlwaysEqual>` KV.new(1,"b"),false)
+  \ Testing.checkEquals<?>(KV.new(1,"b") `KeyValueH:lessThanWith<?,?,Int,AlwaysEqual>` KV.new(2,"a"),true)
+
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:lessThanWith<?,?,AlwaysEqual,String>` KV.new(1,"a"),false)
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:lessThanWith<?,?,AlwaysEqual,String>` KV.new(1,"b"),true)
+  \ Testing.checkEquals<?>(KV.new(2,"a") `KeyValueH:lessThanWith<?,?,AlwaysEqual,String>` KV.new(1,"b"),true)
+  \ Testing.checkEquals<?>(KV.new(1,"b") `KeyValueH:lessThanWith<?,?,AlwaysEqual,String>` KV.new(2,"a"),false)
+}
+
+unittest keyValueHEquals {
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:equals<?,?>` KV.new(1,"a"),true)
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:equals<?,?>` KV.new(1,"b"),false)
+  \ Testing.checkEquals<?>(KV.new(2,"a") `KeyValueH:equals<?,?>` KV.new(1,"b"),false)
+  \ Testing.checkEquals<?>(KV.new(1,"b") `KeyValueH:equals<?,?>` KV.new(2,"a"),false)
+}
+
+unittest keyValueHEqualsWith {
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:equalsWith<?,?,Int,AlwaysEqual>` KV.new(1,"a"),true)
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:equalsWith<?,?,Int,AlwaysEqual>` KV.new(1,"b"),true)
+  \ Testing.checkEquals<?>(KV.new(2,"a") `KeyValueH:equalsWith<?,?,Int,AlwaysEqual>` KV.new(1,"b"),false)
+  \ Testing.checkEquals<?>(KV.new(1,"b") `KeyValueH:equalsWith<?,?,Int,AlwaysEqual>` KV.new(2,"a"),false)
+
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:equalsWith<?,?,AlwaysEqual,String>` KV.new(1,"a"),true)
+  \ Testing.checkEquals<?>(KV.new(1,"a") `KeyValueH:equalsWith<?,?,AlwaysEqual,String>` KV.new(1,"b"),false)
+  \ Testing.checkEquals<?>(KV.new(2,"a") `KeyValueH:equalsWith<?,?,AlwaysEqual,String>` KV.new(1,"b"),false)
+  \ Testing.checkEquals<?>(KV.new(1,"b") `KeyValueH:equalsWith<?,?,AlwaysEqual,String>` KV.new(2,"a"),false)
+}
+
+unittest integrationTest {
+  SearchTree<Int,String> tree1 <- SearchTree<Int,String>.new()
+      .set(1,"d").set(2,"c").set(3,"b").set(4,"a")
+  SearchTree<Int,String> tree2 <- SearchTree<Int,String>.new()
+      .set(1,"a").set(2,"b").set(3,"c").set(4,"d")
+
+  \ Testing.checkEquals<?>(tree1.defaultOrder() `OrderH:equalsWith<?,KVEquals<Int,AlwaysEqual>>`    tree2.defaultOrder(),true)
+  \ Testing.checkEquals<?>(tree1.defaultOrder() `OrderH:equalsWith<?,KVEquals<Int,String>>`         tree2.defaultOrder(),false)
+  \ Testing.checkEquals<?>(tree1.defaultOrder() `OrderH:equalsWith<?,KVEquals<AlwaysEqual,String>>` tree2.defaultOrder(),false)
+}
+
+concrete KVEquals<#k,#v> {
+  defines Equals<KeyValue<Int,String>>
+  #k defines Equals<Int>
+  #v defines Equals<String>
+}
+
+define KVEquals {
+  equals (x,y) {
+    return x `KeyValueH:equalsWith<?,?,#k,#v>` y
+  }
+}
+
+concrete KV {
+  refines KeyValue<Int,String>
+
+  @type new (Int,String) -> (KV)
+}
+
+define KV {
+  $ReadOnly[key,value]$
+
+  @value Int key
+  @value String value
+
+  new (k,v) {
+    return KV{ k, v }
+  }
+
+  getKey () {
+    return key
+  }
+
+  getValue () {
+    return value
+  }
+}
diff --git a/lib/container/helpers.0rx b/lib/container/helpers.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/helpers.0rx
@@ -0,0 +1,45 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+define KeyValueH {
+  lessThan (x,y) {
+    return lessThanWith<#k,#v,#k,#v>(x,y)
+  }
+
+  lessThanWith (x,y) {
+    if (x.getKey() `#kk.lessThan` y.getKey()) {
+      return true
+    } elif (y.getKey() `#kk.lessThan` x.getKey()) {
+      return false
+    } else {
+      return x.getValue() `#vv.lessThan` y.getValue()
+    }
+  }
+
+  equals (x,y) {
+    return equalsWith<#k,#v,#k,#v>(x,y)
+  }
+
+  equalsWith (x,y) {
+    if (!(x.getKey() `#kk.equals` y.getKey())) {
+      return false
+    } else {
+      return x.getValue() `#vv.equals` y.getValue()
+    }
+  }
+}
diff --git a/lib/container/interfaces.0rp b/lib/container/interfaces.0rp
--- a/lib/container/interfaces.0rp
+++ b/lib/container/interfaces.0rp
@@ -51,6 +51,29 @@
   get (#k) -> (optional #v)
 }
 
+// Writing to a set of values.
+//
+// Params:
+// - #k: The type stored in the set.
+@value interface SetWriter<#k|> {
+  add (#k)    -> (#self)
+  remove (#k) -> (#self)
+}
+
+// Checking a set of values.
+//
+// Params:
+// - #k: The type stored in the set.
+@value interface SetReader<#k|> {
+  member (#k) -> (Bool)
+}
+
+// Factory for creating a new key-value pair.
+@type interface KVFactory<#k,#v|> {
+  // Create a new pair from the provided key and value.
+  newNode (#k,#v) -> (#self)
+}
+
 // A single key-value pair from a KVReader.
 @value interface KeyValue<|#k,#v> {
   // Get the key.
diff --git a/lib/container/search-tree.0rp b/lib/container/search-tree.0rp
--- a/lib/container/search-tree.0rp
+++ b/lib/container/search-tree.0rp
@@ -22,6 +22,7 @@
 // - #k: The key type.
 // - #v: The value type.
 concrete SearchTree<#k,#v> {
+  refines Container
   refines DefaultOrder<KeyValue<#k,#v>>
   refines KVWriter<#k,#v>
   refines KVReader<#k,#v>
@@ -60,23 +61,4 @@
   // - If the key does not exist in the SearchTree, the position right after
   //   where it would be (in the reverse direction) is returned.
   @value getReverse (#k) -> (optional Order<KeyValue<#k,#v>>)
-}
-
-// A validated binary search tree for key-value storage.
-//
-// Params:
-// - #k: The key type.
-// - #v: The value type.
-//
-// Notes:
-// - This is the same as SearchTree, but it validates the tree structure every
-//   time there is a modification, which is extremely inefficient. This should
-//   therefore only be used in tests.
-concrete ValidatedTree<#k|#v|> {
-  refines KVWriter<#k,#v>
-  refines KVReader<#k,#v>
-  #k defines LessThan<#k>
-
-  // Create an empty tree.
-  @type new () -> (#self)
 }
diff --git a/lib/container/search-tree.0rt b/lib/container/search-tree.0rt
--- a/lib/container/search-tree.0rt
+++ b/lib/container/search-tree.0rt
@@ -21,7 +21,7 @@
 }
 
 unittest integrationTest {
-  [KVWriter<Int,Int>&KVReader<Int,Int>] tree <- ValidatedTree<Int,Int>.new()
+  ValidatedTree<Int,Int> tree <- ValidatedTree<Int,Int>.new()
   Int count <- 30
   $ReadOnly[count]$
 
@@ -29,6 +29,7 @@
   traverse (Counter.zeroIndexed(count) -> Int i) {
     Int new <- ((i + 13) * 3547) % count
     \ tree.set(new,i)
+    \ Testing.checkEquals<?>(tree.size(),i+1)
   }
 
   // Check and remove values.
@@ -54,6 +55,7 @@
           .writeTo(SimpleOutput.error())
     }
     \ tree.remove(new)
+    \ Testing.checkEquals<?>(tree.size(),count-i-1)
   }
 }
 
@@ -76,7 +78,7 @@
     index <- index+1
   }
 
-  \ Testing.checkEquals<?>(index,20)
+  \ Testing.checkEquals<?>(index,max)
 }
 
 unittest reverseOrder {
@@ -91,7 +93,7 @@
   }
 
   // Validate the traversal order.
-  Int index <- 20
+  Int index <- max
   traverse (tree.reverseOrder() -> KeyValue<Int,Int> entry) {
     index <- index-1
     \ Testing.checkEquals<?>(entry.getKey(),index)
@@ -120,7 +122,7 @@
       \ Testing.checkEquals<?>((entry.getValue()*hash)%max,entry.getKey())
       index <- index+1
     }
-    \ Testing.checkEquals<?>(index,20)
+    \ Testing.checkEquals<?>(index,max)
   }
 }
 
diff --git a/lib/container/search-tree.0rx b/lib/container/search-tree.0rx
--- a/lib/container/search-tree.0rx
+++ b/lib/container/search-tree.0rx
@@ -17,187 +17,149 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define SearchTree {
-  @value optional Node<#k,#v> root
+  @value AutoBinaryTree<SearchTreeNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>> tree
 
   new () {
-    return #self{ empty }
+    return #self{ AutoBinaryTree<SearchTreeNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>>.new() }
   }
 
+  size () {
+    return tree.size()
+  }
+
   set (k,v) {
-    root <- Node<#k,#v>.insert(root,k,v)
+    \ tree.set(k,v)
     return self
   }
 
   remove (k) {
-    root <- Node<#k,#v>.delete(root,k)
+    \ tree.remove(k)
     return self
   }
 
   get (k) {
-    return Node<#k,#v>.find(root,k)
+    return tree.get(k)
   }
 
   defaultOrder () {
-    return ForwardTreeOrder:create<?,?>(root)
+    return ForwardTreeOrder:create<?,?>(tree.getRoot())
   }
 
   reverseOrder () {
-    return ReverseTreeOrder:create<?,?>(root)
+    return ReverseTreeOrder:create<?,?>(tree.getRoot())
   }
 
   getForward (k) {
-    return ForwardTreeOrder:seek<?,?>(k,root)
+    return ForwardTreeOrder:seek<?,?>(k,tree.getRoot())
   }
 
   getReverse (k) {
-    return ReverseTreeOrder:seek<?,?>(k,root)
+    return ReverseTreeOrder:seek<?,?>(k,tree.getRoot())
   }
 }
 
 define ValidatedTree {
-  @value optional Node<#k,#v> root
+  @value AutoBinaryTree<SearchTreeNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>> tree
 
   new () {
-    return #self{ empty }
+    return #self{ AutoBinaryTree<SearchTreeNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>>.new() }
   }
 
+  size () {
+    return tree.size()
+  }
+
   set (k,v) {
-    root <- Node<#k,#v>.insert(root,k,v)
-    \ Node<#k,#v>.validate(root)
+    \ tree.set(k,v)
+    \ validate(tree.getRoot())
     return self
   }
 
   remove (k) {
-    root <- Node<#k,#v>.delete(root,k)
-    \ Node<#k,#v>.validate(root)
+    \ tree.remove(k)
+    \ validate(tree.getRoot())
     return self
   }
 
   get (k) {
-    return Node<#k,#v>.find(root,k)
-  }
-}
-
-concrete Node<#k,#v> {
-  #k defines LessThan<#k>
-
-  @type insert (optional Node<#k,#v>,#k,#v) -> (optional Node<#k,#v>)
-  @type delete (optional Node<#k,#v>,#k) -> (optional Node<#k,#v>)
-  @type find (optional Node<#k,#v>,#k) -> (optional #v)
-  @type validate (optional Node<#k,#v>) -> ()
-
-  @value getLower () -> (optional Node<#k,#v>)
-  @value getHigher () -> (optional Node<#k,#v>)
-  @value getKey () -> (#k)
-  @value getValue () -> (#v)
-}
-
-define Node {
-  $ReadOnly[key]$
-
-  @value Int height
-  @value #k key
-  @value #v value
-  @value optional Node<#k,#v> lower
-  @value optional Node<#k,#v> higher
-
-  insert (node,k,v) {
-    if (!present(node)) {
-      return #self{ 1, k, v, empty, empty }
-    }
-    Node<#k,#v> node2 <- require(node)
-    if (k `#k.lessThan` node2.getKey()) {
-      \ node2.setLower(insert(node2.getLower(),k,v))
-      return rebalance(node2)
-    } elif (node2.getKey() `#k.lessThan` k) {
-      \ node2.setHigher(insert(node2.getHigher(),k,v))
-      return rebalance(node2)
-    } else {
-      \ node2.setValue(v)
-      return node2
-    }
-  }
-
-  delete (node,k) {
-    if (!present(node)) {
-      return empty
-    }
-    Node<#k,#v> node2 <- require(node)
-    if (k `#k.lessThan` node2.getKey()) {
-      \ node2.setLower(delete(node2.getLower(),k))
-      return rebalance(node2)
-    } elif (node2.getKey() `#k.lessThan` k) {
-      \ node2.setHigher(delete(node2.getHigher(),k))
-      return rebalance(node2)
-    } else {
-      return rebalance(removeNode(node2))
-    }
-  }
-
-  find (node,k) {
-    if (present(node)) {
-      scoped {
-        Node<#k,#v> node2 <- require(node)
-      } in if (k `#k.lessThan` node2.getKey()) {
-        return find(node2.getLower(),k)
-      } elif (node2.getKey() `#k.lessThan` k) {
-        return find(node2.getHigher(),k)
-      } else {
-        return node2.getValue()
-      }
-    } else {
-      return empty
-    }
+    return tree.get(k)
   }
 
+  @type validate (optional BinaryTreeNode<#k,#v>) -> ()
   validate (node) {
     if (present(node)) {
-      \ require(node).validateOrder()
-      \ require(node).validateBalance()
+      \ validateOrder(require(node))
+      \ validateBalance(require(node))
     }
   }
 
-  getLower () { return lower }
-
-  getHigher () { return higher }
-
-  getKey () { return key }
-
-  getValue () { return value }
-
-  @value validateOrder () -> ()
-  validateOrder () {
-    if (present(lower)) {
-      if (!(require(lower).getKey() `#k.lessThan` getKey())) {
+  @type validateOrder (BinaryTreeNode<#k,#v>) -> ()
+  validateOrder (node) {
+    if (present(node.getLower())) {
+      if (!(require(node.getLower()).getKey() `#k.lessThan` node.getKey())) {
         fail("bad lower order")
       }
-      \ require(lower).validateOrder()
+      \ validateOrder(require(node.getLower()))
     }
-    if (present(higher)) {
-      if (!(getKey() `#k.lessThan` require(higher).getKey())) {
+    if (present(node.getHigher())) {
+      if (!(node.getKey() `#k.lessThan` require(node.getHigher()).getKey())) {
         fail("bad higher order")
       }
-      \ require(higher).validateOrder()
+      \ validateOrder(require(node.getHigher()))
     }
   }
 
-  @value validateBalance () -> ()
-  validateBalance () {
+  @type validateBalance (BinaryTreeNode<#k,#v>) -> ()
+  validateBalance (node) {
     scoped {
-      Int balance <- getBalance()
+      Int balance <- 0
+      if (present(node.getLower())) {
+        balance <- balance-require(node.getLower()).getHeight()
+      }
+      if (present(node.getHigher())) {
+        balance <- balance+require(node.getHigher()).getHeight()
+      }
+      $ReadOnly[balance]$
     } in if (balance > 1 || balance < -1) {
       fail("out of balance: " + balance.formatted())
     }
-    if (present(lower)) {
-      \ require(lower).validateBalance()
+    if (present(node.getLower())) {
+      \ validateBalance(require(node.getLower()))
     }
-    if (present(higher)) {
-      \ require(higher).validateBalance()
+    if (present(node.getHigher())) {
+      \ validateBalance(require(node.getHigher()))
     }
   }
+}
 
-  @value updateHeight () -> ()
-  updateHeight () {
+concrete SearchTreeNode<#k,#v> {
+  defines KVFactory<#k,#v>
+  refines BalancedTreeNode<SearchTreeNode<#k,#v>,#k,#v>
+}
+
+define SearchTreeNode {
+  $ReadOnly[key]$
+
+  @value Int height
+  @value #k key
+  @value #v value
+  @value optional #self lower
+  @value optional #self higher
+
+  newNode (k,v) {
+    return #self{ 1, k, v, empty, empty }
+  }
+
+  getLower ()   { return lower }
+  setLower (l)  { lower <- l }
+  getHigher ()  { return higher }
+  setHigher (h) { higher <- h }
+  getKey ()     { return key }
+  getValue ()   { return value }
+  setValue (v)  { value <- v }
+  getHeight ()  { return height }
+
+  updateNode () {
     scoped {
       Int l <- 0
       Int h <- 0
@@ -212,277 +174,5 @@
     } else {
       height <- h + 1
     }
-  }
-
-  @value getBalance () -> (Int)
-  getBalance () {
-    scoped {
-      Int l <- 0
-      Int h <- 0
-      if (present(lower)) {
-        l <- require(lower).getHeight()
-      }
-      if (present(higher)) {
-        h <- require(higher).getHeight()
-      }
-    } in return h - l
-  }
-
-  @type rebalance (optional Node<#k,#v>) -> (optional Node<#k,#v>)
-  rebalance (node) {
-    if (!present(node)) {
-      return empty
-    }
-    Node<#k,#v> node2 <- require(node)
-    \ node2.updateHeight()
-    scoped {
-      Int balance <- node2.getBalance()
-    } in if (balance > 1) {
-      return pivotLower(node2)
-    } elif (balance < -1) {
-      return pivotHigher(node2)
-    } else {
-      return node2
-    }
-  }
-
-  @type pivotHigher (Node<#k,#v>) -> (Node<#k,#v>)
-  pivotHigher (node) (newNode) {
-    if (require(node.getLower()).getBalance() > 0) {
-      \ node.setLower(pivotLower(require(node.getLower())))
-    }
-    newNode <- require(node.getLower())
-    \ node.setLower(newNode.getHigher())
-    \ node.updateHeight()
-    \ newNode.setHigher(node)
-    \ newNode.updateHeight()
-  }
-
-  @type pivotLower (Node<#k,#v>) -> (Node<#k,#v>)
-  pivotLower (node) (newNode) {
-    if (require(node.getHigher()).getBalance() < 0) {
-      \ node.setHigher(pivotHigher(require(node.getHigher())))
-    }
-    newNode <- require(node.getHigher())
-    \ node.setHigher(newNode.getLower())
-    \ node.updateHeight()
-    \ newNode.setLower(node)
-    \ newNode.updateHeight()
-  }
-
-  @type removeNode (Node<#k,#v>) -> (optional Node<#k,#v>)
-  removeNode (node) (newNode) {
-    if (node.getBalance() < 0) {
-      optional Node<#k,#v> temp, newNode <- removeHighest(node.getLower())
-      \ node.setLower(temp)
-    } else {
-      optional Node<#k,#v> temp, newNode <- removeLowest(node.getHigher())
-      \ node.setHigher(temp)
-    }
-    if (present(newNode)) {
-      \ swapChildren(node,require(newNode))
-      \ require(newNode).updateHeight()
-    }
-  }
-
-  @type removeHighest (optional Node<#k,#v>) -> (optional Node<#k,#v>,optional Node<#k,#v>)
-  removeHighest (node) (newNode,removed) {
-    if (!present(node)) {
-      return empty, empty
-    }
-    Node<#k,#v> node2 <- require(node)
-    if (present(node2.getHigher())) {
-      optional Node<#k,#v> temp, removed <- removeHighest(node2.getHigher())
-      \ node2.setHigher(temp)
-      newNode <- rebalance(node2)
-    } else {
-      newNode <- node2.getLower()
-      \ node2.setLower(empty)
-      removed <- node
-    }
-  }
-
-  @type removeLowest (optional Node<#k,#v>) -> (optional Node<#k,#v>,optional Node<#k,#v>)
-  removeLowest (node) (newNode,removed) {
-    if (!present(node)) {
-      return empty, empty
-    }
-    Node<#k,#v> node2 <- require(node)
-    if (present(node2.getLower())) {
-      optional Node<#k,#v> temp, removed <- removeLowest(node2.getLower())
-      \ node2.setLower(temp)
-      newNode <- rebalance(node2)
-    } else {
-      newNode <- node2.getHigher()
-      \ node2.setHigher(empty)
-      removed <- node
-    }
-  }
-
-  @type swapChildren (Node<#k,#v>,Node<#k,#v>) -> ()
-  swapChildren (l,r) {
-    scoped {
-      optional Node<#k,#v> temp <- l.getLower()
-      \ l.setLower(r.getLower())
-    } in \ r.setLower(temp)
-    scoped {
-      optional Node<#k,#v> temp <- l.getHigher()
-      \ l.setHigher(r.getHigher())
-    } in \ r.setHigher(temp)
-  }
-
-  @value getHeight () -> (Int)
-  getHeight () { return height }
-
-  @value setValue (#v) -> ()
-  setValue (v) { value <- v }
-
-  @value setLower (optional Node<#k,#v>) -> ()
-  setLower (l) { lower <- l }
-
-  @value setHigher (optional Node<#k,#v>) -> ()
-  setHigher (h) { higher <- h }
-}
-
-concrete TreeKeyValue<|#k,#v> {
-  refines KeyValue<#k,#v>
-
-  @category create<#k,#v> (#k,#v) -> (TreeKeyValue<#k,#v>)
-}
-
-define TreeKeyValue {
-  $ReadOnly[key,value]$
-
-  @value #k key
-  @value #v value
-
-  create (key,value) {
-    return TreeKeyValue<#k,#v>{ key, value }
-  }
-
-  getKey () {
-    return key
-  }
-
-  getValue () {
-    return value
-  }
-}
-
-concrete ForwardTreeOrder<|#k,#v> {
-  refines Order<KeyValue<#k,#v>>
-
-  @category create<#k,#v> (optional Node<#k,#v>) -> (optional ForwardTreeOrder<#k,#v>)
-
-  @category seek<#k,#v>
-    #k defines LessThan<#k>
-  (#k,optional Node<#k,#v>) -> (optional ForwardTreeOrder<#k,#v>)
-}
-
-define ForwardTreeOrder {
-  $ReadOnly[node,prev]$
-
-  @value Node<#k,#v> node
-  @value optional ForwardTreeOrder<#k,#v> prev
-
-  create (node) (current) {
-    optional Node<#k,#v> node2 <- node
-    current <- empty
-    while (present(node2)) {
-      current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
-      node2 <- require(node2).getLower()
-    }
-  }
-
-  seek (key,node) (current) {
-    optional Node<#k,#v> node2 <- node
-    current <- empty
-    while (present(node2)) {
-      if (require(node2).getKey() `#k.lessThan` key) {
-        // Skip node2 in the traversal, since it's before key.
-        node2 <- require(node2).getHigher()
-      } elif (key `#k.lessThan` require(node2).getKey()) {
-        current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
-        node2 <- require(node2).getLower()
-      } else {
-        current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
-        break
-      }
-    }
-  }
-
-  next () (current) {
-    // Algorithm:
-    // 1. Pop self from the stack.
-    // 2. Traverse lower to the bottom starting from the higher child of self.
-    optional Node<#k,#v> node2 <- node.getHigher()
-    current <- prev
-    while (present(node2)) {
-      current <- ForwardTreeOrder<#k,#v>{ require(node2), current }
-      node2 <- require(node2).getLower()
-    }
-  }
-
-  get () {
-    return TreeKeyValue:create<?,?>(node.getKey(),node.getValue())
-  }
-}
-
-concrete ReverseTreeOrder<|#k,#v> {
-  refines Order<KeyValue<#k,#v>>
-
-  @category create<#k,#v> (optional Node<#k,#v>) -> (optional ReverseTreeOrder<#k,#v>)
-
-  @category seek<#k,#v>
-    #k defines LessThan<#k>
-  (#k,optional Node<#k,#v>) -> (optional ReverseTreeOrder<#k,#v>)
-}
-
-define ReverseTreeOrder {
-  $ReadOnly[node,prev]$
-
-  @value Node<#k,#v> node
-  @value optional ReverseTreeOrder<#k,#v> prev
-
-  create (node) (current) {
-    optional Node<#k,#v> node2 <- node
-    current <- empty
-    while (present(node2)) {
-      current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
-      node2 <- require(node2).getHigher()
-    }
-  }
-
-  seek (key,node) (current) {
-    optional Node<#k,#v> node2 <- node
-    current <- empty
-    while (present(node2)) {
-      if (require(node2).getKey() `#k.lessThan` key) {
-        current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
-        node2 <- require(node2).getHigher()
-      } elif (key `#k.lessThan` require(node2).getKey()) {
-        // Skip node2 in the traversal, since it's after key.
-        node2 <- require(node2).getLower()
-      } else {
-        current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
-        break
-      }
-    }
-  }
-
-  next () (current) {
-    // Algorithm:
-    // 1. Pop self from the stack.
-    // 2. Traverse higher to the bottom starting from the lower child of self.
-    optional Node<#k,#v> node2 <- node.getLower()
-    current <- prev
-    while (present(node2)) {
-      current <- ReverseTreeOrder<#k,#v>{ require(node2), current }
-      node2 <- require(node2).getHigher()
-    }
-  }
-
-  get () {
-    return TreeKeyValue:create<?,?>(node.getKey(),node.getValue())
   }
 }
diff --git a/lib/container/sorting.0rp b/lib/container/sorting.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/sorting.0rp
@@ -0,0 +1,53 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+// Sorting of sequences of values.
+concrete Sorting {
+  // In-place unstable sorting of a random-access container.
+  //
+  // Params:
+  // - #x: Element type managed by the container.
+  //
+  // Notes:
+  // - Worst-case performance is O(n log n).
+  // - Worst-case storage is O(1).
+  @category sort<#x>
+    #x defines LessThan<#x>
+  ([ReadAt<#x>&WriteAt<#x>]) -> ()
+
+  // In-place unstable sorting of a random-access container.
+  //
+  // Params:
+  // - #x: Element type managed by the container.
+  // - #xx: Comparator type providing the LessThan<#x> comparison.
+  //
+  // Notes:
+  // - Worst-case performance is O(n log n).
+  // - Worst-case storage is O(1).
+  //
+  // Example:
+  //
+  //   // Sort myIntContainer in reverse order. (Reversed is from lib/util.)
+  //   \ Sorting:sortWith<?,Reversed<Int>>(myIntContainer)
+  @category sortWith<#x,#xx>
+    #xx defines LessThan<#x>
+  ([ReadAt<#x>&WriteAt<#x>]) -> ()
+
+  // In-place order reversal of a random-access container.
+  @category reverse<#x> ([ReadAt<#x>&WriteAt<#x>]) -> ()
+}
diff --git a/lib/container/sorting.0rt b/lib/container/sorting.0rt
new file mode 100644
--- /dev/null
+++ b/lib/container/sorting.0rt
@@ -0,0 +1,154 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+testcase "Sorting tests" {
+  success
+}
+
+unittest sort {
+  TestSequence original <- TestSequence.create()
+  TestSequence expected <- TestSequence.create()
+  Int hash <- 269
+  Int size <- 137
+  $ReadOnly[hash,size]$
+
+  traverse (Counter.zeroIndexed(size) -> Int i) {
+    \ original.append((i*hash)%size/3)
+    \ expected.append(i/3)
+  }
+
+  \ Sorting:sort<?>(original)
+  \ Testing.checkEquals<?>(original,expected)
+}
+
+unittest sortWith {
+  TestSequence original <- TestSequence.create()
+  TestSequence expected <- TestSequence.create()
+  Int hash <- 313
+  Int size <- 197
+  $ReadOnly[hash,size]$
+
+  traverse (Counter.revZeroIndexed(size) -> Int i) {
+    \ original.append((i*hash)%size/3)
+    \ expected.append(i/3)
+  }
+
+  \ Sorting:sortWith<?,Reversed<Int>>(original)
+  \ Testing.checkEquals<?>(original,expected)
+}
+
+unittest sortEmpty {
+  TestSequence original <- TestSequence.create()
+  TestSequence expected <- TestSequence.create()
+
+  \ Sorting:sort<?>(original)
+  \ Testing.checkEquals<?>(original,expected)
+}
+
+unittest reverseEvenSize {
+  TestSequence original <- TestSequence.create()
+  TestSequence expected <- TestSequence.create()
+  Int size <- 100
+  $ReadOnly[size]$
+
+  traverse (Counter.zeroIndexed(size) -> Int i) {
+    \ expected.append(i)
+  }
+
+  traverse (Counter.revZeroIndexed(size) -> Int i) {
+    \ original.append(i)
+  }
+
+  \ Sorting:reverse<?>(original)
+  \ Testing.checkEquals<?>(original,expected)
+}
+
+unittest reverseOddSize {
+  TestSequence original <- TestSequence.create()
+  TestSequence expected <- TestSequence.create()
+  Int size <- 101
+  $ReadOnly[size]$
+
+  traverse (Counter.zeroIndexed(size) -> Int i) {
+    \ expected.append(i)
+  }
+
+  traverse (Counter.revZeroIndexed(size) -> Int i) {
+    \ original.append(i)
+  }
+
+  \ Sorting:reverse<?>(original)
+  \ Testing.checkEquals<?>(original,expected)
+}
+
+unittest reverseEmpty {
+  TestSequence original <- TestSequence.create()
+  TestSequence expected <- TestSequence.create()
+
+  \ Sorting:reverse<?>(original)
+  \ Testing.checkEquals<?>(original,expected)
+}
+
+concrete TestSequence {
+  refines Formatted
+  refines ReadAt<Int>
+  refines WriteAt<Int>
+  refines Append<Int>
+  defines Equals<TestSequence>
+
+  @type create () -> (TestSequence)
+}
+
+define TestSequence {
+  @value Vector<Int> seq
+
+  create () {
+    return TestSequence{ Vector:create<Int>() }
+  }
+
+  formatted () {
+    [Append<String>&Build<String>] builder <- String.builder()
+    \ builder.append("{")
+    traverse (seq.defaultOrder() -> Formatted x) {
+      \ builder.append(" ").append(x.formatted())
+    }
+    return builder.append(" }").build()
+  }
+
+  size () {
+    return seq.size()
+  }
+
+  readAt (i) {
+    return seq.readAt(i)
+  }
+
+  writeAt (i,x) {
+    \ seq.writeAt(i,x)
+    return self
+  }
+
+  append (x) {
+    \ seq.append(x)
+    return self
+  }
+
+  equals (x,y) {
+    return x `ReadAtH:equals<?>` y
+  }
+}
diff --git a/lib/container/sorting.0rx b/lib/container/sorting.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/sorting.0rx
@@ -0,0 +1,104 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+define Sorting {
+  sort (seq) {
+    \ sortWith<#x,#x>(seq)
+  }
+
+  sortWith (seq) {
+    \ HeapSort<#x,#xx>.inPlace(seq)
+  }
+
+  reverse (seq) {
+    traverse (Counter.zeroIndexed(seq.size()/2) -> Int i) {
+      Int j <- seq.size()-i-1
+      #x temp <- seq.readAt(i)
+      \ seq.writeAt(i,seq.readAt(j))
+      \ seq.writeAt(j,temp)
+    }
+  }
+}
+
+// Putting the params at the top level allows helpers to be called without
+// needing to pass the params every time.
+concrete HeapSort<#x,#xx> {
+  #xx defines LessThan<#x>
+
+  @type inPlace ([ReadAt<#x>&WriteAt<#x>]) -> ()
+}
+
+define HeapSort {
+  $ReadOnly[seq]$
+
+  @value [ReadAt<#x>&WriteAt<#x>] seq
+
+  inPlace (seq) {
+    \ (#self{ seq }).execute()
+  }
+
+  @value execute () -> ()
+  execute () {
+    // Convert the container to a heap.
+    traverse (Counter.revZeroIndexed(seq.size()/2) -> Int i) {
+      \ sift(i,seq.size())
+    }
+
+    // Traverse the heap and populate the container in place.
+    traverse (Counter.revZeroIndexed(seq.size()) -> Int i) {
+      $ReadOnly[i]$
+      \ swap(0,i)
+      \ sift(0,i)
+    }
+  }
+
+  @value sift (Int,Int) -> ()
+  sift (start,size) {
+    scoped {
+      Int last         <- start
+      Int indexLargest <- last
+      $Hidden[start]$
+    } in while (2*last+1 < size) {
+      $ReadOnly[last]$
+      Int left  <- 2*last+1
+      Int right <- 2*last+2
+      $ReadOnly[left,right]$
+      if (seq.readAt(indexLargest) `#xx.lessThan` seq.readAt(left)) {
+        indexLargest <- left
+      }
+      if (right < size && seq.readAt(indexLargest) `#xx.lessThan` seq.readAt(right)) {
+        indexLargest <- right
+      }
+      if (indexLargest == last) {
+        break
+      }
+    } update {
+      \ swap(last,indexLargest)
+      last <- indexLargest
+    }
+  }
+
+  @value swap (Int,Int) -> ()
+  swap (i,j) {
+    if (i != j) {
+      #x temp <- seq.readAt(i)
+      \ seq.writeAt(i,seq.readAt(j))
+      \ seq.writeAt(j,temp)
+    }
+  }
+}
diff --git a/lib/container/src/Extension_Vector.cpp b/lib/container/src/Extension_Vector.cpp
--- a/lib/container/src/Extension_Vector.cpp
+++ b/lib/container/src/Extension_Vector.cpp
@@ -16,7 +16,6 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-#include <mutex>
 #include <vector>
 
 #include "category-source.hpp"
@@ -104,7 +103,6 @@
 
   ReturnTuple Call_append(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
     TRACE_FUNCTION("Vector.append")
-    std::lock_guard<std::mutex> lock(mutex);
     const S<TypeValue>& Var_arg1 = (args.At(0));
     values.push_back(Var_arg1);
     return ReturnTuple(Var_self);
@@ -121,7 +119,6 @@
 
   ReturnTuple Call_pop(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
     TRACE_FUNCTION("Vector.pop")
-    std::lock_guard<std::mutex> lock(mutex);
     if (values.empty()) {
       BUILTIN_FAIL(Box_String(PrimString_FromLiteral("no elements left to pop")))
     }
@@ -132,7 +129,6 @@
 
   ReturnTuple Call_push(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
     TRACE_FUNCTION("Vector.push")
-    std::lock_guard<std::mutex> lock(mutex);
     const S<TypeValue>& Var_arg1 = (args.At(0));
     values.push_back(Var_arg1);
     return ReturnTuple(Var_self);
@@ -140,7 +136,6 @@
 
   ReturnTuple Call_readAt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
     TRACE_FUNCTION("Vector.readAt")
-    std::lock_guard<std::mutex> lock(mutex);
     const PrimInt Var_arg1 = (args.At(0))->AsInt();
     if (Var_arg1 < 0 || Var_arg1 >= values.size()) {
       FAIL() << "index " << Var_arg1 << " is out of bounds";
@@ -150,13 +145,11 @@
 
   ReturnTuple Call_size(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
     TRACE_FUNCTION("Vector.size")
-    std::lock_guard<std::mutex> lock(mutex);
     return ReturnTuple(Box_Int(values.size()));
   }
 
   ReturnTuple Call_writeAt(const S<TypeValue>& Var_self, const ParamTuple& params, const ValueTuple& args) final {
     TRACE_FUNCTION("Vector.writeAt")
-    std::lock_guard<std::mutex> lock(mutex);
     const PrimInt Var_arg1 = (args.At(0))->AsInt();
     const S<TypeValue>& Var_arg2 = (args.At(1));
     if (Var_arg1 < 0 || Var_arg1 >= values.size()) {
@@ -166,7 +159,6 @@
     return ReturnTuple(Var_self);
   }
 
-  std::mutex mutex;
   VectorType values;
 };
 
diff --git a/lib/container/tree-set.0rp b/lib/container/tree-set.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/tree-set.0rp
@@ -0,0 +1,59 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+concrete TreeSet<#k> {
+  refines Container
+  refines DefaultOrder<#k>
+  refines SetReader<#k>
+  refines SetWriter<#k>
+  #k defines LessThan<#k>
+
+  // Create a new set.
+  @type new () -> (#self)
+
+  // Traverse in the default order.
+  //
+  // Notes:
+  // - This is from DefaultOrder, but is made explicit for documentation.
+  // - Traversal of the entire set is amortized O(n); however, the cost of any
+  //   particular iteration could be up to O(log n).
+  // - The overall memory cost is O(log n).
+  @value defaultOrder () -> (optional Order<#k>)
+
+  // Traverse the set in the reverse order of defaultOrder().
+  //
+  // Notes:
+  // - Traversal of the entire set is amortized O(n); however, the cost of any
+  //   particular iteration could be up to O(log n).
+  // - The overall memory cost is O(log n).
+  @value reverseOrder () -> (optional Order<#k>)
+
+  // Start forward traversal (same as defaultOrder()) from the specified key.
+  //
+  // Notes:
+  // - If the key does not exist in the SearchTree, the position right after
+  //   where it would be (in the forward direction) is returned.
+  @value getForward (#k) -> (optional Order<#k>)
+
+  // Start reverse traversal (same as reverseOrder()) from the specified key.
+  //
+  // Notes:
+  // - If the key does not exist in the SearchTree, the position right after
+  //   where it would be (in the reverse direction) is returned.
+  @value getReverse (#k) -> (optional Order<#k>)
+}
diff --git a/lib/container/tree-set.0rt b/lib/container/tree-set.0rt
new file mode 100644
--- /dev/null
+++ b/lib/container/tree-set.0rt
@@ -0,0 +1,172 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+testcase "TreeSet tests" {
+  success
+}
+
+unittest addAndRemove {
+  TreeSet<Int> set <- TreeSet<Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the set in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ set.add((i*hash)%max)
+    \ Testing.checkEquals<?>(set.size(),i+1)
+  }
+
+  // Remove only the odd elements.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    if (i%2 == 1) {
+      // Remove it twice to ensure idempotence.
+      \ set.remove(i).remove(i)
+    }
+    \ Testing.checkEquals<?>(set.size(),max-(i+1)/2)
+  }
+
+  // Validate set membership.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ Testing.checkEquals<?>(set.member(i),i%2 == 0)
+  }
+}
+
+unittest defaultOrder {
+  TreeSet<Int> set <- TreeSet<Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the set in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ set.add((i*hash)%max)
+  }
+
+  // Validate the traversal order.
+  Int index <- 0
+  traverse (set.defaultOrder() -> Int entry) {
+    \ Testing.checkEquals<?>(entry,index)
+    index <- index+1
+  }
+
+  \ Testing.checkEquals<?>(index,max)
+}
+
+unittest reverseOrder {
+  TreeSet<Int> set <- TreeSet<Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the set in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ set.add((i*hash)%max)
+  }
+
+  // Validate the traversal order.
+  Int index <- max
+  traverse (set.reverseOrder() -> Int entry) {
+    index <- index-1
+    \ Testing.checkEquals<?>(entry,index)
+  }
+
+  \ Testing.checkEquals<?>(index,0)
+}
+
+unittest getForward {
+  TreeSet<Int> set <- TreeSet<Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the set in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ set.add((i*hash)%max)
+  }
+
+  // Validate the traversal order from every starting point.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    Int index <- i
+    traverse (set.getForward(index) -> Int entry) {
+      \ Testing.checkEquals<?>(entry,index)
+      index <- index+1
+    }
+    \ Testing.checkEquals<?>(index,max)
+  }
+}
+
+unittest getForwardNotFound {
+  TreeSet<Int> set <- TreeSet<Int>.new()
+  \ set.add(1).add(3).add(5)
+
+  scoped {
+    optional Order<Int> start <- set.getForward(4)
+  } in if (!present(start)) {
+    fail("Failed")
+  } else {
+    \ Testing.checkEquals<?>(require(start).get(),5)
+  }
+
+  scoped {
+    optional Order<Int> start <- set.getForward(6)
+  } in if (present(start)) {
+    fail("Failed")
+  }
+}
+
+unittest getReverse {
+  TreeSet<Int> set <- TreeSet<Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the set in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ set.add((i*hash)%max)
+  }
+
+  // Validate the traversal order from every starting point.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    Int index <- i
+    traverse (set.getReverse(index) -> Int entry) {
+      \ Testing.checkEquals<?>(entry,index)
+      index <- index-1
+    }
+    \ Testing.checkEquals<?>(index,-1)
+  }
+}
+
+unittest getReverseNotFound {
+  TreeSet<Int> set <- TreeSet<Int>.new()
+  \ set.add(1).add(3).add(5)
+
+  scoped {
+    optional Order<Int> start <- set.getReverse(2)
+  } in if (!present(start)) {
+    fail("Failed")
+  } else {
+    \ Testing.checkEquals<?>(require(start).get(),1)
+  }
+
+  scoped {
+    optional Order<Int> start <- set.getReverse(0)
+  } in if (present(start)) {
+    fail("Failed")
+  }
+}
diff --git a/lib/container/tree-set.0rx b/lib/container/tree-set.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/tree-set.0rx
@@ -0,0 +1,92 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+define TreeSet {
+  @value SearchTree<#k,any> tree
+
+  new () {
+    return #self{ SearchTree<#k,any>.new() }
+  }
+
+  size () {
+    return tree.size()
+  }
+
+  add (k) {
+    \ tree.set(k,Void.void())
+    return self
+  }
+
+  remove (k) {
+    \ tree.remove(k)
+    return self
+  }
+
+  member (k) {
+    return present(tree.get(k))
+  }
+
+  defaultOrder () {
+    return TreeSetOrder<#k>.wrap(tree.defaultOrder())
+  }
+
+  reverseOrder () {
+    return TreeSetOrder<#k>.wrap(tree.reverseOrder())
+  }
+
+  getForward (k) {
+    return TreeSetOrder<#k>.wrap(tree.getForward(k))
+  }
+
+  getReverse (k) {
+    return TreeSetOrder<#k>.wrap(tree.getReverse(k))
+  }
+}
+
+concrete TreeSetOrder<#k> {
+  refines Order<#k>
+
+  @type wrap (optional Order<KeyValue<#k,any>>) -> (optional TreeSetOrder<#k>)
+}
+
+define TreeSetOrder {
+  @value Order<KeyValue<#k,any>> order
+
+  wrap (order) {
+    if (!present(order)) {
+      return empty
+    } else {
+      return #self{ require(order) }
+    }
+  }
+
+  next () {
+    scoped {
+      optional Order<KeyValue<#k,any>> order2 <- order.next()
+    } in if (present(order2)) {
+      order <- require(order2)
+      return self
+    } else {
+      return empty
+    }
+  }
+
+  get () {
+    return order.get().getKey()
+  }
+}
diff --git a/lib/container/validated-tree.0rp b/lib/container/validated-tree.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/validated-tree.0rp
@@ -0,0 +1,34 @@
+/* -----------------------------------------------------------------------------
+Copyright 2019-2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+$ModuleOnly$
+
+// A validated binary search tree for key-value storage.
+//
+// Notes:
+// - This is the same as SearchTree, but it validates the tree structure every
+//   time there is a modification, which is extremely inefficient. This should
+//   therefore only be used in tests.
+concrete ValidatedTree<#k|#v|> {
+  refines Container
+  refines KVWriter<#k,#v>
+  refines KVReader<#k,#v>
+  #k defines LessThan<#k>
+
+  @type new () -> (#self)
+}
diff --git a/lib/math/.zeolite-module b/lib/math/.zeolite-module
--- a/lib/math/.zeolite-module
+++ b/lib/math/.zeolite-module
@@ -1,7 +1,9 @@
 root: "../.."
 path: "lib/math"
 private_deps: [
+  "lib/container"
   "lib/testing"
+  "lib/util"
 ]
 extra_files: [
   category_source {
diff --git a/lib/math/categorical-tree.0rp b/lib/math/categorical-tree.0rp
new file mode 100644
--- /dev/null
+++ b/lib/math/categorical-tree.0rp
@@ -0,0 +1,72 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+// A categorical distribution represented as a tree.
+//
+// Params:
+// #c: The type of category (i.e., object) used in the distribution.
+//
+// Notes:
+// - CategoricalTree is intended for use in random sampling of arbitrary objects
+//   based on relative weights that can be dynamically set.
+// - The distribution automatically updates every time a weight is changed. This
+//   can be used for updating Bayesian observations while simultaneously being
+//   able to sample values from the distribution.
+// - The complexity of most operations is O(log n) with n distinct #c values.
+// - The required storage space is independent of the weights; it only depends
+//   on the number of distinct #c values with non-zero weights.
+concrete CategoricalTree<#c> {
+  refines ReadAt<#c>
+  #c defines LessThan<#c>
+
+  // Create a new distribution.
+  @type new () -> (#self)
+
+  // Get the sum of all weights in the distribution.
+  @value getTotal () -> (Int)
+
+  // Set the relative weight of a category.
+  //
+  // Notes:
+  // - The weight must not be negative.
+  @value setWeight (#c,Int) -> (#self)
+
+  // Get the relative weight of a category.
+  //
+  // Notes:
+  // - If the category isn't in the tree, its weight is 0.
+  @value getWeight (#c) -> (Int)
+
+  // Return the category at the given offset.
+  //
+  // Notes:
+  // - The offset must be within [0,getTotal()). A uniform selection in that
+  //   range will provide samples that follow the categorical distribution
+  //   corresponding to the relative weights of the respective #c.
+  // - The return value is deterministic. If you were to iterate over
+  //   [0,getTotal()), you'd get an increasing sequence of all #c in the
+  //   CategoricalTree, each repeated the number of times indicated by its
+  //   respective weight.
+  @value locate (Int) -> (#c)
+
+  // Identical to locate(). (From ReadAt.)
+  @value readAt (Int) -> (#c)
+
+  // Identical to getTotal(). (From ReadAt/Container.)
+  @value size () -> (Int)
+}
diff --git a/lib/math/categorical-tree.0rt b/lib/math/categorical-tree.0rt
new file mode 100644
--- /dev/null
+++ b/lib/math/categorical-tree.0rt
@@ -0,0 +1,164 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+testcase "CategoricalTree tests" {
+  success
+}
+
+unittest simpleInsertion {
+  CategoricalTree<Int> tree <- CategoricalTree<Int>.new()
+      .setWeight(1,3)
+      .setWeight(6,2)
+      .setWeight(3,1)
+      .setWeight(5,4)
+      .setWeight(4,3)
+      .setWeight(2,7)
+
+  Vector<Int> expected <- Vector:create<Int>()
+      .push(1).push(1).push(1)
+      .push(2).push(2).push(2).push(2).push(2).push(2).push(2)
+      .push(3)
+      .push(4).push(4).push(4)
+      .push(5).push(5).push(5).push(5)
+      .push(6).push(6)
+
+  \ Testing.checkEquals<?>(tree.getTotal(),expected.size())
+  \ Testing.checkEquals<?>(tree.getWeight(1),3)
+  \ Testing.checkEquals<?>(tree.getWeight(2),7)
+  \ Testing.checkEquals<?>(tree.getWeight(3),1)
+  \ Testing.checkEquals<?>(tree.getWeight(4),3)
+  \ Testing.checkEquals<?>(tree.getWeight(5),4)
+  \ Testing.checkEquals<?>(tree.getWeight(6),2)
+
+  traverse (Counter.zeroIndexed(tree.getTotal()) -> Int pos) {
+    \ Testing.checkOptional<?>(tree.locate(pos),expected.readAt(pos))
+  }
+}
+
+unittest modifySizes {
+  CategoricalTree<Int> tree <- CategoricalTree<Int>.new()
+      .setWeight(1,3)
+      .setWeight(6,2)
+      .setWeight(3,1)
+      .setWeight(5,4)
+      .setWeight(4,3)
+      .setWeight(2,7)
+      .setWeight(7,3)
+      // modifications start here
+      .setWeight(5,1)
+      .setWeight(3,0)
+
+  Vector<Int> expected <- Vector:create<Int>()
+      .push(1).push(1).push(1)
+      .push(2).push(2).push(2).push(2).push(2).push(2).push(2)
+      .push(4).push(4).push(4)
+      .push(5)
+      .push(6).push(6)
+      .push(7).push(7).push(7)
+
+  \ Testing.checkEquals<?>(tree.getTotal(),expected.size())
+  \ Testing.checkEquals<?>(tree.getWeight(1),3)
+  \ Testing.checkEquals<?>(tree.getWeight(2),7)
+  \ Testing.checkEquals<?>(tree.getWeight(3),0)
+  \ Testing.checkEquals<?>(tree.getWeight(4),3)
+  \ Testing.checkEquals<?>(tree.getWeight(5),1)
+  \ Testing.checkEquals<?>(tree.getWeight(6),2)
+  \ Testing.checkEquals<?>(tree.getWeight(7),3)
+
+  traverse (Counter.zeroIndexed(tree.getTotal()) -> Int pos) {
+    \ Testing.checkOptional<?>(tree.locate(pos),expected.readAt(pos))
+  }
+
+  // Use ReadAt aliases for the same functionality.
+  traverse (Counter.zeroIndexed(tree.size()) -> Int pos) {
+    \ Testing.checkOptional<?>(tree.readAt(pos),expected.readAt(pos))
+  }
+}
+
+unittest integrationTest {
+  ValidatedTree<Int> tree      <- ValidatedTree<Int>.new()
+  SearchTree<Int,Int> expected <- SearchTree<Int,Int>.new()
+
+  Int count   <- 137
+  Int maxSize <- 15
+  Int hash1   <- 379
+  Int hash2   <- 457
+  $ReadOnly[count,maxSize,hash1,hash2]$
+
+  // Populate the tree with arbitrary data.
+  traverse (Counter.zeroIndexed(count) -> Int i) {
+    \ tree.setWeight((i*hash1)%count,(i*hash2)%maxSize)
+  }
+
+  // Overwrite the tree with real data.
+  traverse (Counter.zeroIndexed(count) -> Int i) {
+    Int key <- (i*hash2)%count
+    Int size <- (i*hash1)%maxSize
+    \ tree.setWeight(key,size)
+    \ expected.set(key,size)
+  }
+
+  optional Order<KeyValue<Int,Int>> node <- expected.defaultOrder()
+  Int key       <- 0
+  Int remaining <- 0
+  traverse (Counter.zeroIndexed(tree.getTotal()) -> Int pos) {
+    while (remaining < 1) {
+      key       <- require(node).get().getKey()
+      remaining <- require(node).get().getValue()
+      node <- require(node).next()
+    }
+    $ReadOnly[key]$
+    $Hidden[node]$
+    \ Testing.checkEquals<?>(tree.locate(pos),key)
+    remaining <- remaining-1
+  }
+
+  // NOTE: Order does not refine Formatted => can't use checkEmpty here.
+  \ Testing.checkEquals<?>(present(node),false)
+  \ Testing.checkEquals<?>(remaining,0)
+}
+
+
+testcase "negative size crashes" {
+  crash
+  require "size.+negative"
+}
+
+unittest test {
+  \ CategoricalTree<Int>.new().setWeight(0,-10)
+}
+
+
+testcase "negative position crashes" {
+  crash
+  require "position.+negative"
+}
+
+unittest test {
+  \ CategoricalTree<Int>.new().locate(-10)
+}
+
+
+testcase "position past end crashes" {
+  crash
+  require "position.+total"
+}
+
+unittest test {
+  \ CategoricalTree<Int>.new().setWeight(0,1).locate(1)
+}
diff --git a/lib/math/categorical-tree.0rx b/lib/math/categorical-tree.0rx
new file mode 100644
--- /dev/null
+++ b/lib/math/categorical-tree.0rx
@@ -0,0 +1,231 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+define CategoricalTree {
+  @value AutoBinaryTree<CategoricalTreeNode<#c>,#c,Int,CategoricalSearch<#c>> tree
+
+  new () {
+    return #self{ AutoBinaryTree<CategoricalTreeNode<#c>,#c,Int,CategoricalSearch<#c>>.new() }
+  }
+
+  getTotal () {
+    return CategoricalTreeNode<#c>.tryTotal(tree.getRoot())
+  }
+
+  setWeight (cat,size) {
+    if (size < 0) {
+      fail("size must not be negative")
+    } elif (size > 0) {
+      \ tree.set(cat,size)
+    } else {
+      \ tree.remove(cat)
+    }
+    return self
+  }
+
+  getWeight (cat) {
+    scoped {
+      optional Int size <- tree.get(cat)
+    } in if (present(size)) {
+      return require(size)
+    } else {
+      return 0
+    }
+  }
+
+  locate (pos) {
+    if (pos < 0) {
+      fail("position must not be negative")
+    } elif (pos >= getTotal()) {
+      fail("position must be strictly less than the total")
+    } else {
+      return require(CategoricalTreeNode<#c>.findPosition(pos,tree.getRoot()))
+    }
+  }
+
+  readAt (pos) {
+    return locate(pos)
+  }
+
+  size () {
+    return getTotal()
+  }
+}
+
+define ValidatedTree {
+  @value AutoBinaryTree<CategoricalTreeNode<#c>,#c,Int,CategoricalSearch<#c>> tree
+
+  new () {
+    return #self{ AutoBinaryTree<CategoricalTreeNode<#c>,#c,Int,CategoricalSearch<#c>>.new() }
+  }
+
+  getTotal () {
+    return CategoricalTreeNode<#c>.tryTotal(tree.getRoot())
+  }
+
+  setWeight (cat,size) {
+    if (size < 0) {
+      fail("size must not be negative")
+    } elif (size > 0) {
+      \ tree.set(cat,size)
+    } else {
+      \ tree.remove(cat)
+    }
+    \ validateTotal(tree.getRoot())
+    return self
+  }
+
+  getWeight (cat) {
+    scoped {
+      optional Int size <- tree.get(cat)
+    } in if (present(size)) {
+      return require(size)
+    } else {
+      return 0
+    }
+  }
+
+  locate (pos) {
+    if (pos < 0) {
+      fail("position must not be negative")
+    } elif (pos >= getTotal()) {
+      fail("position must be strictly less than the total")
+    } else {
+      return require(CategoricalTreeNode<#c>.findPosition(pos,tree.getRoot()))
+    }
+  }
+
+  @type validateTotal (optional CategoricalSearch<#c>) -> ()
+  validateTotal (node) {
+    if (present(node)) {
+      CategoricalSearch<#c> node2 <- require(node)
+      \ validateTotal(node2.getLower())
+      \ validateTotal(node2.getHigher())
+      Int size   <- node2.getValue()
+      Int lower  <- CategoricalTreeNode<#c>.tryTotal(node2.getLower())
+      Int higher <- CategoricalTreeNode<#c>.tryTotal(node2.getHigher())
+      if (node2.getTotal() != size+lower+higher) {
+        fail(String.builder()
+            .append("bad total: ")
+            .append(size.formatted())
+            .append("+")
+            .append(lower.formatted())
+            .append("+")
+            .append(higher.formatted())
+            .append(" != ")
+            .append(node2.getTotal().formatted())
+            .build())
+      }
+    }
+  }
+}
+
+@value interface CategoricalSearch<|#c> {
+  refines BinaryTreeNode<#c,Int>
+
+  getTotal () -> (Int)
+}
+
+concrete CategoricalTreeNode<#c> {
+  defines KVFactory<#c,Int>
+  refines CategoricalSearch<#c>
+  refines BalancedTreeNode<CategoricalTreeNode<#c>,#c,Int>
+
+  @type findPosition (Int,optional CategoricalSearch<#c>) -> (optional #c)
+  @type tryTotal (optional CategoricalSearch<#c>) -> (Int)
+
+  @value getTotal () -> (Int)
+}
+
+define CategoricalTreeNode {
+  $ReadOnly[key]$
+
+  @value Int height
+  @value #c key
+  @value Int size
+  @value Int total
+  @value optional #self lower
+  @value optional #self higher
+
+  findPosition (pos,node) (cat) {
+    cat <- empty
+    if (present(node)) {
+      scoped {
+        Int pos2 <- pos
+        $Hidden[pos]$
+        CategoricalSearch<#c> node2 <- require(node)
+        Int lower <- tryTotal(node2.getLower())
+        Int size <- node2.getValue()
+        $ReadOnly[node2,lower,size]$
+      } in if (pos2 < node2.getTotal()) {
+        if (pos2 < lower) {
+          // pos2 is in the 1st (lower) of the 3 sections.
+          return findPosition(pos2,node2.getLower())
+        }
+        pos2 <- pos2-lower
+        if (pos2 < size) {
+          // pos2 is in the 2nd (middle) of the 3 sections.
+          return node2.getKey()
+        }
+        pos2 <- pos2-size
+          // pos2 is in the 3rd (higher) of the 3 sections.
+        return findPosition(pos2,node2.getHigher())
+      }
+    }
+  }
+
+  tryTotal (node) {
+    if (present(node)) {
+      return require(node).getTotal()
+    } else {
+      return 0
+    }
+  }
+
+  newNode (k,v) {
+    return #self{ 1, k, v, v, empty, empty }
+  }
+
+  getLower ()   { return lower }
+  setLower (l)  { lower <- l }
+  getHigher ()  { return higher }
+  setHigher (h) { higher <- h }
+  getKey ()     { return key }
+  getValue ()   { return size }
+  setValue (v)  { size <- v }
+  getHeight ()  { return height }
+  getTotal ()   { return total }
+
+  updateNode () {
+    scoped {
+      Int l <- 0
+      Int h <- 0
+      if (present(lower)) {
+        l <- require(lower).getHeight()
+      }
+      if (present(higher)) {
+        h <- require(higher).getHeight()
+      }
+    } in if (l > h) {
+      height <- l + 1
+    } else {
+      height <- h + 1
+    }
+    total <- size+tryTotal(lower)+tryTotal(higher)
+  }
+}
diff --git a/lib/math/math.0rt b/lib/math/math.0rt
new file mode 100644
--- /dev/null
+++ b/lib/math/math.0rt
@@ -0,0 +1,135 @@
+/* -----------------------------------------------------------------------------
+Copyright 2020 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+testcase "sanity check all functions" {
+  success
+}
+
+unittest cos {
+  \ Testing.checkBetween<?>(Math.cos(2.0),-0.42,-0.41)
+}
+
+unittest sin {
+  \ Testing.checkBetween<?>(Math.sin(2.0),0.90,0.91)
+}
+
+unittest tan {
+  \ Testing.checkBetween<?>(Math.tan(2.0),-2.19,-2.18)
+}
+
+unittest acos {
+  \ Testing.checkBetween<?>(Math.acos(0.5),1.04,1.05)
+}
+
+unittest asin {
+  \ Testing.checkBetween<?>(Math.asin(0.5),0.52,0.53)
+}
+
+unittest atan {
+  \ Testing.checkBetween<?>(Math.atan(0.5),0.46,0.47)
+}
+
+unittest cosh {
+  \ Testing.checkBetween<?>(Math.cosh(2.0),3.76,3.77)
+}
+
+unittest sinh {
+  \ Testing.checkBetween<?>(Math.sinh(2.0),3.62,3.63)
+}
+
+unittest tanh {
+  \ Testing.checkBetween<?>(Math.tanh(2.0),0.96,0.97)
+}
+
+unittest acosh {
+  \ Testing.checkBetween<?>(Math.acosh(2.0),1.31,1.32)
+}
+
+unittest asinh {
+  \ Testing.checkBetween<?>(Math.asinh(2.0),1.44,1.45)
+}
+
+unittest atanh {
+  \ Testing.checkBetween<?>(Math.atanh(0.5),0.54,0.55)
+}
+
+unittest exp {
+  \ Testing.checkBetween<?>(Math.exp(1.0),2.71,2.72)
+}
+
+unittest log {
+  \ Testing.checkBetween<?>(Math.log(9.0),2.19,2.20)
+}
+
+unittest log10 {
+  \ Testing.checkBetween<?>(Math.log10(100.0),1.99,2.01)
+}
+
+unittest log2 {
+  \ Testing.checkBetween<?>(Math.log2(8.0),2.99,3.01)
+}
+
+unittest pow {
+  \ Testing.checkBetween<?>(Math.pow(2.0,3.0),7.99,8.01)
+}
+
+unittest sqrt {
+  \ Testing.checkBetween<?>(Math.sqrt(4.0),1.99,2.01)
+}
+
+unittest ceil {
+  \ Testing.checkBetween<?>(Math.ceil(2.2),2.99,3.01)
+}
+
+unittest floor {
+  \ Testing.checkBetween<?>(Math.floor(2.2),1.99,2.01)
+}
+
+unittest fmod {
+  \ Testing.checkBetween<?>(Math.fmod(7.0,4.0),2.99,3.01)
+}
+
+unittest trunc {
+  \ Testing.checkBetween<?>(Math.trunc(2.2),1.99,2.01)
+}
+
+unittest round {
+  \ Testing.checkBetween<?>(Math.round(2.7),2.99,3.01)
+}
+
+unittest fabs {
+  \ Testing.checkBetween<?>(Math.fabs(-10.0),9.99,10.01)
+}
+
+unittest isinf {
+  if (!Math.isinf(Math.log(0.0))) {
+    fail("Failed")
+  }
+}
+
+unittest isnan {
+  if (!Math.isnan(Math.sqrt(-1.0))) {
+    fail("Failed")
+  }
+}
+
+unittest abs {
+  \ Testing.checkEquals<?>(Math.abs(-10),10)
+  \ Testing.checkEquals<?>(Math.abs(10),10)
+  \ Testing.checkEquals<?>(Math.abs(0),0)
+}
diff --git a/lib/math/tests.0rt b/lib/math/tests.0rt
deleted file mode 100644
--- a/lib/math/tests.0rt
+++ /dev/null
@@ -1,135 +0,0 @@
-/* -----------------------------------------------------------------------------
-Copyright 2020 Kevin P. Barry
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
------------------------------------------------------------------------------ */
-
-// Author: Kevin P. Barry [ta0kira@gmail.com]
-
-testcase "sanity check all functions" {
-  success
-}
-
-unittest cos {
-  \ Testing.checkBetween<?>(Math.cos(2.0),-0.42,-0.41)
-}
-
-unittest sin {
-  \ Testing.checkBetween<?>(Math.sin(2.0),0.90,0.91)
-}
-
-unittest tan {
-  \ Testing.checkBetween<?>(Math.tan(2.0),-2.19,-2.18)
-}
-
-unittest acos {
-  \ Testing.checkBetween<?>(Math.acos(0.5),1.04,1.05)
-}
-
-unittest asin {
-  \ Testing.checkBetween<?>(Math.asin(0.5),0.52,0.53)
-}
-
-unittest atan {
-  \ Testing.checkBetween<?>(Math.atan(0.5),0.46,0.47)
-}
-
-unittest cosh {
-  \ Testing.checkBetween<?>(Math.cosh(2.0),3.76,3.77)
-}
-
-unittest sinh {
-  \ Testing.checkBetween<?>(Math.sinh(2.0),3.62,3.63)
-}
-
-unittest tanh {
-  \ Testing.checkBetween<?>(Math.tanh(2.0),0.96,0.97)
-}
-
-unittest acosh {
-  \ Testing.checkBetween<?>(Math.acosh(2.0),1.31,1.32)
-}
-
-unittest asinh {
-  \ Testing.checkBetween<?>(Math.asinh(2.0),1.44,1.45)
-}
-
-unittest atanh {
-  \ Testing.checkBetween<?>(Math.atanh(0.5),0.54,0.55)
-}
-
-unittest exp {
-  \ Testing.checkBetween<?>(Math.exp(1.0),2.71,2.72)
-}
-
-unittest log {
-  \ Testing.checkBetween<?>(Math.log(9.0),2.19,2.20)
-}
-
-unittest log10 {
-  \ Testing.checkBetween<?>(Math.log10(100.0),1.99,2.01)
-}
-
-unittest log2 {
-  \ Testing.checkBetween<?>(Math.log2(8.0),2.99,3.01)
-}
-
-unittest pow {
-  \ Testing.checkBetween<?>(Math.pow(2.0,3.0),7.99,8.01)
-}
-
-unittest sqrt {
-  \ Testing.checkBetween<?>(Math.sqrt(4.0),1.99,2.01)
-}
-
-unittest ceil {
-  \ Testing.checkBetween<?>(Math.ceil(2.2),2.99,3.01)
-}
-
-unittest floor {
-  \ Testing.checkBetween<?>(Math.floor(2.2),1.99,2.01)
-}
-
-unittest fmod {
-  \ Testing.checkBetween<?>(Math.fmod(7.0,4.0),2.99,3.01)
-}
-
-unittest trunc {
-  \ Testing.checkBetween<?>(Math.trunc(2.2),1.99,2.01)
-}
-
-unittest round {
-  \ Testing.checkBetween<?>(Math.round(2.7),2.99,3.01)
-}
-
-unittest fabs {
-  \ Testing.checkBetween<?>(Math.fabs(-10.0),9.99,10.01)
-}
-
-unittest isinf {
-  if (!Math.isinf(Math.log(0.0))) {
-    fail("Failed")
-  }
-}
-
-unittest isnan {
-  if (!Math.isnan(Math.sqrt(-1.0))) {
-    fail("Failed")
-  }
-}
-
-unittest abs {
-  \ Testing.checkEquals<?>(Math.abs(-10),10)
-  \ Testing.checkEquals<?>(Math.abs(10),10)
-  \ Testing.checkEquals<?>(Math.abs(0),0)
-}
diff --git a/lib/math/validated-tree.0rp b/lib/math/validated-tree.0rp
new file mode 100644
--- /dev/null
+++ b/lib/math/validated-tree.0rp
@@ -0,0 +1,36 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+$ModuleOnly$
+
+// A validated categorical tree.
+//
+// Notes:
+// - This is the same as CategoricalTree, but it validates the tree structure
+//   every time there is a modification, which is extremely inefficient. This
+//   should therefore only be used in tests.
+concrete ValidatedTree<#c> {
+  #c defines LessThan<#c>
+
+  @type new () -> (#self)
+
+  @value getTotal  ()       -> (Int)
+  @value setWeight (#c,Int) -> (#self)
+  @value getWeight (#c)     -> (Int)
+  @value locate    (Int)    -> (#c)
+}
diff --git a/lib/thread/condition.0rp b/lib/thread/condition.0rp
--- a/lib/thread/condition.0rp
+++ b/lib/thread/condition.0rp
@@ -74,10 +74,8 @@
 //
 // Example:
 //
-//   ConditionWait cond <- // created elsewhere
-//
 //   scoped {
-//     MutexLock lock <- MutexLock.lock(cond)
+//     MutexLock lock <- MutexLock.lock(conditionResume)
 //   } cleanup {
 //     \ lock.freeResource()
 //   } in {
@@ -85,7 +83,7 @@
 //   }
 //
 //   // signal threads to continue
-//   \ cond.resumeAll()
+//   \ conditionResume.resumeAll()
 @value interface ConditionResume {
   refines Mutex
 
diff --git a/lib/thread/thread.0rp b/lib/thread/thread.0rp
--- a/lib/thread/thread.0rp
+++ b/lib/thread/thread.0rp
@@ -28,13 +28,15 @@
   // Blocks the current thread until the Thread exits.
   //
   // Notes:
-  // - Check isRunning() before calling. There might be a crash otherwise.
+  // - There might be a crash if join() or detach() were already called, or if
+  //   the thread was never started in the first place.
   join () -> (#self)
 
   // Disassociates the running thread from this handle.
   //
   // Notes:
-  // - Check isRunning() before calling. There might be a crash otherwise.
+  // - There might be a crash if join() or detach() were already called, or if
+  //   the thread was never started in the first place.
   detach () -> (#self)
 }
 
diff --git a/lib/util/counters.0rp b/lib/util/counters.0rp
--- a/lib/util/counters.0rp
+++ b/lib/util/counters.0rp
@@ -20,8 +20,10 @@
 concrete Counter {
   // Create a sequence of indices for a Container of the given size.
   @type zeroIndexed (Int) -> (optional Order<Int>)
+  // Create a reversed sequence of indices for a Container of the given size.
+  @type revZeroIndexed (Int) -> (optional Order<Int>)
   // Create an unbounded Int sequence.
-  @type unlimited   ()    -> (optional Order<Int>)
+  @type unlimited () -> (optional Order<Int>)
 }
 
 // Creates an Order<#x> that repeats a single value, for use with traverse.
@@ -30,4 +32,22 @@
   @category times<#y>     (#y,Int) -> (optional Order<#y>)
   // Repeat the value an unlimited number of times.
   @category unlimited<#y> (#y)     -> (optional Order<#y>)
+}
+
+// Determines min and max of two values.
+concrete Ranges {
+  // Get the lower value. (Defaults to the first value.)
+  @category min<#x>
+    #x defines LessThan<#x>
+  (#x,#x) -> (#x)
+
+  // Get the higher value. (Defaults to the second value.)
+  @category max<#x>
+    #x defines LessThan<#x>
+  (#x,#x) -> (#x)
+
+  // Determine the lower and higher values. (Defaults to the same order.)
+  @category minMax<#x>
+    #x defines LessThan<#x>
+  (#x,#x) -> (#x,#x)
 }
diff --git a/lib/util/counters.0rt b/lib/util/counters.0rt
--- a/lib/util/counters.0rt
+++ b/lib/util/counters.0rt
@@ -31,6 +31,17 @@
   \ Testing.checkEquals<?>(index,10)
 }
 
+unittest revZeroIndexed {
+  Int index <- 10
+
+  traverse (Counter.revZeroIndexed(10) -> Int i) {
+    index <- index-1
+    \ Testing.checkEquals<?>(i,index)
+  }
+
+  \ Testing.checkEquals<?>(index,0)
+}
+
 unittest unlimitedCount {
   Int index <- 0
 
@@ -66,4 +77,40 @@
   }
 
   \ Testing.checkEquals<?>(index,10)
+}
+
+unittest minMax {
+  scoped {
+    Int x, Int y <- Ranges:minMax<?>(10,3)
+  } in {
+    \ Testing.checkEquals<?>(x,3)
+    \ Testing.checkEquals<?>(y,10)
+  }
+
+  scoped {
+    Int x, Int y <- Ranges:minMax<?>(3,10)
+  } in {
+    \ Testing.checkEquals<?>(x,3)
+    \ Testing.checkEquals<?>(y,10)
+  }
+}
+
+unittest min {
+  scoped {
+    Int x <- Ranges:min<?>(10,3)
+  } in \ Testing.checkEquals<?>(x,3)
+
+  scoped {
+    Int x <- Ranges:min<?>(3,10)
+  } in \ Testing.checkEquals<?>(x,3)
+}
+
+unittest max {
+  scoped {
+    Int x <- Ranges:max<?>(10,3)
+  } in \ Testing.checkEquals<?>(x,10)
+
+  scoped {
+    Int x <- Ranges:max<?>(3,10)
+  } in \ Testing.checkEquals<?>(x,10)
 }
diff --git a/lib/util/counters.0rx b/lib/util/counters.0rx
--- a/lib/util/counters.0rx
+++ b/lib/util/counters.0rx
@@ -17,28 +17,39 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define Counter {
-  $ReadOnly[max]$
+  $ReadOnly[limit,incr]$
 
   refines Order<Int>
 
   @value Int current
-  @value Int max
+  @value optional Int limit
+  @value Int incr
 
   zeroIndexed (max) {
     if (max <= 0) {
       return empty
     } else {
-      return Counter{ 0, max }
+      return Counter{ 0, max, 1 }
     }
   }
 
+  revZeroIndexed (max) {
+    if (max <= 0) {
+      return empty
+    } else {
+      return Counter{ max-1, -1, -1 }
+    }
+  }
+
   unlimited () {
-    return Counter{ 0, -1 }
+    return Counter{ 0, empty, 1 }
   }
 
   next () {
-    if (max < 0 || current+1 < max) {
-      current <- current+1
+    if (!present(limit) ||
+        (incr < 0 && current+incr > require(limit)) ||
+        (incr > 0 && current+incr < require(limit))) {
+      current <- current+incr
       return self
     } else {
       return empty
@@ -51,13 +62,13 @@
 }
 
 define Repeat {
-  $ReadOnly[max,value]$
+  $ReadOnly[limit,value]$
 
   refines Order<#x>
 
   @value #x value
   @value Int current
-  @value Int max
+  @value optional Int limit
 
   times (value,max) {
     if (max <= 0) {
@@ -68,11 +79,11 @@
   }
 
   unlimited (value) {
-    return Repeat<#y>{ value, 0, -1 }
+    return Repeat<#y>{ value, 0, empty }
   }
 
   next () {
-    if (max < 0 || current+1 < max) {
+    if (!present(limit) || current+1 < require(limit)) {
       current <- current+1
       return self
     } else {
@@ -82,5 +93,24 @@
 
   get () {
     return value
+  }
+}
+
+define Ranges {
+  min (x,y) (z) {
+    z, _ <- minMax<?>(x,y)
+  }
+
+  max (x,y) (z) {
+    _, z <- minMax<?>(x,y)
+  }
+
+  minMax (x,y) {
+    // NOTE: Using y < x makes the default x, y when they are equal.
+    if (y `#x.lessThan` x) {
+      return y, x
+    } else {
+      return x, y
+    }
   }
 }
diff --git a/lib/util/helpers.0rp b/lib/util/helpers.0rp
new file mode 100644
--- /dev/null
+++ b/lib/util/helpers.0rp
@@ -0,0 +1,141 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+// Helpers to extend functionality to the Order<#x> built-in.
+concrete OrderH {
+  // Extend lessThan, based on position-wise comparisons.
+  //
+  // Notes:
+  // - If one sequence is a strict prefix of the other, that sequence will
+  //   evaluate as less-than the other.
+  //
+  // Example:
+  //
+  //   Bool lt <- x.defaultOrder() `OrderH:lessThan<?>` y.defaultOrder()
+  @category lessThan<#x>
+    #x defines LessThan<#x>
+  (optional Order<#x>,optional Order<#x>) -> (Bool)
+
+  // The same as lessThan, but with a custom comparator.
+  //
+  // Params:
+  // - #x: Element type managed by the container.
+  // - #xx: Comparator type providing the LessThan<#x> comparison.
+  //
+  // Example:
+  //
+  //   Bool lt <- x.defaultOrder() `OrderH:lessThanWith<?,Reversed<Int>>` y.defaultOrder()
+  @category lessThanWith<#x,#xx>
+    #xx defines LessThan<#x>
+  (optional Order<#x>,optional Order<#x>) -> (Bool)
+
+  // Extend equals, based on position-wise comparisons.
+  //
+  // Example:
+  //
+  //   Bool eq <- x.defaultOrder() `OrderH:equals<?>` y.defaultOrder()
+  @category equals<#x>
+    #x defines Equals<#x>
+  (optional Order<#x>,optional Order<#x>) -> (Bool)
+
+  // The same as equals, but with a custom comparator.
+  //
+  // Params:
+  // - #x: Element type managed by the container.
+  // - #xx: Comparator type providing the Equals<#x> comparison.
+  //
+  // Example:
+  //
+  //   Bool eq <- x.defaultOrder() `OrderH:equalsWith<?,BySize>` y.defaultOrder()
+  @category equalsWith<#x,#xx>
+    #xx defines Equals<#x>
+  (optional Order<#x>,optional Order<#x>) -> (Bool)
+}
+
+// Helpers to extend functionality to the ReadAt<#x> built-in.
+concrete ReadAtH {
+  // Extend lessThan, based on position-wise comparisons.
+  //
+  // Notes:
+  // - If one sequence is a strict prefix of the other, that sequence will
+  //   evaluate as less-than the other.
+  //
+  // Example:
+  //
+  //   Bool lt <- x `ReadAtH:lessThan<?>` y
+  @category lessThan<#x>
+    #x defines LessThan<#x>
+  (ReadAt<#x>,ReadAt<#x>) -> (Bool)
+
+  // The same as lessThan, but with a custom comparator.
+  //
+  // Params:
+  // - #x: Element type managed by the container.
+  // - #xx: Comparator type providing the LessThan<#x> comparison.
+  //
+  // Example:
+  //
+  //   Bool lt <- x `ReadAtH:lessThanWith<?,Reversed<Int>>` y
+  @category lessThanWith<#x,#xx>
+    #xx defines LessThan<#x>
+  (ReadAt<#x>,ReadAt<#x>) -> (Bool)
+
+  // Extend equals, based on position-wise comparisons.
+  //
+  // Example:
+  //
+  //   Bool eq <- x `ReadAtH:equals<?>` y
+  @category equals<#x>
+    #x defines Equals<#x>
+  (ReadAt<#x>,ReadAt<#x>) -> (Bool)
+
+  // The same as equals, but with a custom comparator.
+  //
+  // Params:
+  // - #x: Element type managed by the container.
+  // - #xx: Comparator type providing the Equals<#x> comparison.
+  //
+  // Example:
+  //
+  //   Bool eq <- x `ReadAtH:equalsWith<?,BySize>` y
+  @category equalsWith<#x,#xx>
+    #xx defines Equals<#x>
+  (ReadAt<#x>,ReadAt<#x>) -> (Bool)
+}
+
+// Reverse an existing LessThan<#x> comparison.
+concrete Reversed<#x|> {
+  defines LessThan<#x>
+  #x defines LessThan<#x>
+}
+
+// Base ordering on container size alone.
+concrete BySize {
+  defines Equals<Container>
+  defines LessThan<Container>
+}
+
+// Always evaluate objects as equal.
+//
+// Notes:
+// - This will allow comparing any two objects with each other (e.g., Int and
+//   ErrorOr<String>), regardless of if they are of compatible types.
+concrete AlwaysEqual {
+  defines Equals<any>
+  defines LessThan<any>
+}
diff --git a/lib/util/helpers.0rt b/lib/util/helpers.0rt
new file mode 100644
--- /dev/null
+++ b/lib/util/helpers.0rt
@@ -0,0 +1,139 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+testcase "comparator helper tests" {
+  success
+}
+
+unittest orderHLessThan {
+  \ Testing.checkEquals<?>(("").defaultOrder()    `OrderH:lessThan<?>` ("").defaultOrder(),   false)
+  \ Testing.checkEquals<?>(("a").defaultOrder()   `OrderH:lessThan<?>` ("abc").defaultOrder(),true)
+  \ Testing.checkEquals<?>(("b").defaultOrder()   `OrderH:lessThan<?>` ("abc").defaultOrder(),false)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:lessThan<?>` ("abc").defaultOrder(),false)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:lessThan<?>` ("a").defaultOrder(),  false)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:lessThan<?>` ("b").defaultOrder(),  true)
+}
+
+unittest orderHLessThanWith {
+  \ Testing.checkEquals<?>(("").defaultOrder()    `OrderH:lessThanWith<?,Reversed<Char>>` ("").defaultOrder(),   false)
+  \ Testing.checkEquals<?>(("a").defaultOrder()   `OrderH:lessThanWith<?,Reversed<Char>>` ("abc").defaultOrder(),true)
+  \ Testing.checkEquals<?>(("b").defaultOrder()   `OrderH:lessThanWith<?,Reversed<Char>>` ("abc").defaultOrder(),true)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:lessThanWith<?,Reversed<Char>>` ("abc").defaultOrder(),false)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:lessThanWith<?,Reversed<Char>>` ("a").defaultOrder(),  false)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:lessThanWith<?,Reversed<Char>>` ("b").defaultOrder(),  false)
+}
+
+unittest orderHEquals {
+  \ Testing.checkEquals<?>(("").defaultOrder()    `OrderH:equals<?>` ("").defaultOrder(),   true)
+  \ Testing.checkEquals<?>(("a").defaultOrder()   `OrderH:equals<?>` ("abc").defaultOrder(),false)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:equals<?>` ("abc").defaultOrder(),true)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:equals<?>` ("a").defaultOrder(),  false)
+}
+
+unittest orderHEqualsWith {
+  \ Testing.checkEquals<?>(("").defaultOrder()    `OrderH:equalsWith<?,IgnoreCase>` ("").defaultOrder(),   true)
+  \ Testing.checkEquals<?>(("a").defaultOrder()   `OrderH:equalsWith<?,IgnoreCase>` ("ABC").defaultOrder(),false)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:equalsWith<?,IgnoreCase>` ("ABC").defaultOrder(),true)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:equalsWith<?,IgnoreCase>` ("DEF").defaultOrder(),false)
+  \ Testing.checkEquals<?>(("abc").defaultOrder() `OrderH:equalsWith<?,IgnoreCase>` ("A").defaultOrder(),  false)
+}
+
+unittest readAtHLessThan {
+  \ Testing.checkEquals<?>(""    `ReadAtH:lessThan<?>` "",   false)
+  \ Testing.checkEquals<?>("a"   `ReadAtH:lessThan<?>` "abc",true)
+  \ Testing.checkEquals<?>("b"   `ReadAtH:lessThan<?>` "abc",false)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:lessThan<?>` "abc",false)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:lessThan<?>` "a",  false)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:lessThan<?>` "b",  true)
+}
+
+unittest readAtHLessThanWith {
+  \ Testing.checkEquals<?>(""    `ReadAtH:lessThanWith<?,Reversed<Char>>` "",   false)
+  \ Testing.checkEquals<?>("a"   `ReadAtH:lessThanWith<?,Reversed<Char>>` "abc",true)
+  \ Testing.checkEquals<?>("b"   `ReadAtH:lessThanWith<?,Reversed<Char>>` "abc",true)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:lessThanWith<?,Reversed<Char>>` "abc",false)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:lessThanWith<?,Reversed<Char>>` "a",  false)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:lessThanWith<?,Reversed<Char>>` "b",  false)
+}
+
+unittest readAtHEquals {
+  \ Testing.checkEquals<?>(""    `ReadAtH:equals<?>` "",   true)
+  \ Testing.checkEquals<?>("a"   `ReadAtH:equals<?>` "abc",false)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:equals<?>` "abc",true)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:equals<?>` "a",  false)
+}
+
+unittest readAtHEqualsWith {
+  \ Testing.checkEquals<?>(""    `ReadAtH:equalsWith<?,IgnoreCase>` "",   true)
+  \ Testing.checkEquals<?>("a"   `ReadAtH:equalsWith<?,IgnoreCase>` "ABC",false)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:equalsWith<?,IgnoreCase>` "ABC",true)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:equalsWith<?,IgnoreCase>` "DEF",false)
+  \ Testing.checkEquals<?>("abc" `ReadAtH:equalsWith<?,IgnoreCase>` "a",  false)
+}
+
+concrete IgnoreCase {
+  defines Equals<Char>
+}
+
+define IgnoreCase {
+  equals (x,y) {
+    return toLower(x) == toLower(y)
+  }
+
+  @type toLower (Char) -> (Char)
+  toLower (c) {
+    if (c >= 'A' && c <= 'Z') {
+      return (c.asInt() - ('A').asInt() + ('a').asInt()).asChar()
+    } else {
+      return c
+    }
+  }
+}
+
+unittest reversed {
+  \ Testing.checkEquals<?>(0 `Reversed<Int>.lessThan` 0,false)
+  \ Testing.checkEquals<?>(0 `Reversed<Int>.lessThan` 1,false)
+  \ Testing.checkEquals<?>(1 `Reversed<Int>.lessThan` 0,true)
+}
+
+unittest bySizeLessThan {
+  \ Testing.checkEquals<?>(""   `BySize.lessThan` "",  false)
+  \ Testing.checkEquals<?>("b"  `BySize.lessThan` "ac",true)
+  \ Testing.checkEquals<?>("ac" `BySize.lessThan` "b", false)
+  \ Testing.checkEquals<?>("a"  `BySize.lessThan` "b", false)
+}
+
+unittest bySizeEquals {
+  \ Testing.checkEquals<?>(""   `BySize.equals` "",  true)
+  \ Testing.checkEquals<?>("b"  `BySize.equals` "ac",false)
+  \ Testing.checkEquals<?>("ac" `BySize.equals` "b", false)
+  \ Testing.checkEquals<?>("a"  `BySize.equals` "b", true)
+}
+
+unittest alwaysEqualLessThan {
+  \ Testing.checkEquals<?>(""   `AlwaysEqual.lessThan` "",  false)
+  \ Testing.checkEquals<?>("b"  `AlwaysEqual.lessThan` "ac",false)
+  \ Testing.checkEquals<?>("ac" `AlwaysEqual.lessThan` "b", false)
+}
+
+unittest alwaysEqualEquals {
+  \ Testing.checkEquals<?>(""   `AlwaysEqual.equals` "",  true)
+  \ Testing.checkEquals<?>("b"  `AlwaysEqual.equals` "ac",true)
+  \ Testing.checkEquals<?>("ac" `AlwaysEqual.equals` "b", true)
+  \ Testing.checkEquals<?>("a"  `AlwaysEqual.equals` "b", true)
+}
diff --git a/lib/util/helpers.0rx b/lib/util/helpers.0rx
new file mode 100644
--- /dev/null
+++ b/lib/util/helpers.0rx
@@ -0,0 +1,122 @@
+/* -----------------------------------------------------------------------------
+Copyright 2021 Kevin P. Barry
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+----------------------------------------------------------------------------- */
+
+// Author: Kevin P. Barry [ta0kira@gmail.com]
+
+define OrderH {
+  lessThan (x,y) {
+    return lessThanWith<#x,#x>(x,y)
+  }
+
+  lessThanWith (x,y) {
+    optional Order<#x> xx <- x
+    optional Order<#x> yy <- y
+    $Hidden[x,y]$
+    while (present(xx) && present(yy)) {
+      $ReadOnly[xx,yy]$
+      if (require(xx).get() `#xx.lessThan` require(yy).get()) {
+        return true
+      } elif (require(yy).get() `#xx.lessThan` require(xx).get()) {
+        return false
+      }
+    } update {
+      xx <- require(xx).next()
+      yy <- require(yy).next()
+    }
+    return present(yy)
+  }
+
+  equals (x,y) {
+    return equalsWith<#x,#x>(x,y)
+  }
+
+  equalsWith (x,y) {
+    optional Order<#x> xx <- x
+    optional Order<#x> yy <- y
+    $Hidden[x,y]$
+    while (present(xx) && present(yy)) {
+      $ReadOnly[xx,yy]$
+      if (!(require(xx).get() `#xx.equals` require(yy).get())) {
+        return false
+      }
+    } update {
+      xx <- require(xx).next()
+      yy <- require(yy).next()
+    }
+    return !present(xx) && !present(yy)
+  }
+}
+
+define ReadAtH {
+  lessThan (x,y) {
+    return lessThanWith<#x,#x>(x,y)
+  }
+
+  lessThanWith (x,y) {
+    traverse (Counter.zeroIndexed(Ranges:min<?>(x.size(),y.size())) -> Int i) {
+      $ReadOnly[i]$
+      if (x.readAt(i) `#xx.lessThan` y.readAt(i)) {
+        return true
+      } elif (y.readAt(i) `#xx.lessThan` x.readAt(i)) {
+        return false
+      }
+    }
+    return x.size() < y.size()
+  }
+
+  equals (x,y) {
+    return equalsWith<#x,#x>(x,y)
+  }
+
+  equalsWith (x,y) {
+    if (x.size() != y.size()) {
+      return false
+    }
+    traverse (Counter.zeroIndexed(x.size()) -> Int i) {
+      $ReadOnly[i]$
+      if (!(x.readAt(i) `#xx.equals` y.readAt(i))) {
+        return false
+      }
+    }
+    return true
+  }
+}
+
+define Reversed {
+  lessThan (x,y) {
+    return y `#x.lessThan` x
+  }
+}
+
+define BySize {
+  equals (x,y) {
+    return x.size() == y.size()
+  }
+
+  lessThan (x,y) {
+    return x.size() < y.size()
+  }
+}
+
+define AlwaysEqual {
+  equals (_,_) {
+    return true
+  }
+
+  lessThan (_,_) {
+    return false
+  }
+}
diff --git a/lib/util/iterator.0rp b/lib/util/iterator.0rp
--- a/lib/util/iterator.0rp
+++ b/lib/util/iterator.0rp
@@ -16,17 +16,6 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-// Random-access writes. (Like the ReadAt built-in type.)
-//
-// Params:
-// - #x: The value type to be written.
-@value interface WriteAt<#x|> {
-  refines Container
-
-  // Write the value at the given index and return self.
-  writeAt (Int,#x) -> (#self)
-}
-
 // Read at the current position.
 //
 // Params:
diff --git a/lib/util/mutex.0rp b/lib/util/mutex.0rp
--- a/lib/util/mutex.0rp
+++ b/lib/util/mutex.0rp
@@ -40,6 +40,17 @@
 // An automatic mutex lock.
 //
 // Notes:
+// - MutexLock is a bit slower than just calling lock() and unlock() directly,
+//   but it provides error checking, if you don't need optimal code:
+//   - Makes sure that lock() is called exactly once.
+//   - Makes sure that unlock() is called exactly once before the last reference
+//     to the MutexLock is released.
+//   - Provides a trace back to where the MutexLock was originally created, in
+//     addition to where you failed to call freeResource().
+//   - Allows you to mark the mutex as hidden (see example below) so that
+//     subsequent code can't mistakenly lock()/unlock() it.
+// - MutexLock should never be stored as a @value member or @category member; it
+//   should always be scoped to a specific set of operations in a function.
 // - freeResource() must be called; otherwise, there will be a crash. This is to
 //   catch code that could cause a deadlock.
 // - freeResource() must only be called once per MutexLock.
diff --git a/src/Cli/Compiler.hs b/src/Cli/Compiler.hs
--- a/src/Cli/Compiler.hs
+++ b/src/Cli/Compiler.hs
@@ -51,7 +51,6 @@
 import Parser.TextParser (SourceContext)
 import Types.Builtin
 import Types.DefinedCategory
-import Types.Procedure (isLiteralCategory)
 import Types.TypeCategory
 import Types.TypeInstance
 
@@ -109,10 +108,8 @@
   let ns0 = StaticNamespace $ publicNamespace  $ show compilerHash ++ path
   let ns1 = StaticNamespace . privateNamespace $ show time ++ show compilerHash ++ path
   let extensions = concat $ map getSourceCategories es
-  let ss = filter (not . isLiteralCategory) extensions
-  let ex = filter isLiteralCategory extensions
   cs <- loadModuleGlobals resolver p (ns0,ns1) ps Nothing deps1' deps2
-  let cm = createLanguageModule ex ss em cs
+  let cm = createLanguageModule extensions em cs
   let cs2 = filter (not . hasCodeVisibility FromDependency) cs
   let pc = map (getCategoryName . wvData) $ filter (not . hasCodeVisibility ModuleOnly) cs2
   let tc = map (getCategoryName . wvData) $ filter (hasCodeVisibility ModuleOnly)       cs2
@@ -267,8 +264,8 @@
   [CompileMetadata] -> [CompileMetadata] -> TrackedErrorsIO ()
 createModuleTemplates resolver p d deps1 deps2 = do
   (ps,xs,_) <- findSourceFiles p d
-  (LanguageModule _ _ _ cs0 ps0 ts0 cs1 ps1 ts1 _ _ _) <-
-    fmap (createLanguageModule [] [] Map.empty) $ loadModuleGlobals resolver p (PublicNamespace,PrivateNamespace) ps Nothing deps1 deps2
+  (LanguageModule _ _ _ cs0 ps0 ts0 cs1 ps1 ts1 _ _) <-
+    fmap (createLanguageModule [] Map.empty) $ loadModuleGlobals resolver p (PublicNamespace,PrivateNamespace) ps Nothing deps1 deps2
   xs' <- zipWithContents resolver p xs
   ds <- mapCompilerM parseInternalSource xs'
   let ds2 = concat $ map (\(_,_,d2) -> d2) ds
@@ -299,7 +296,7 @@
   mapCompilerM_ showSkipped $ filter (not . isTestAllowed) $ cmTestFiles m
   ts' <- zipWithContents resolver p $ map (d </>) $ filter isTestAllowed $ cmTestFiles m
   path <- errorFromIO $ canonicalizePath (p </> d)
-  cm <- fmap (createLanguageModule [] [] em) $ loadModuleGlobals resolver path (NoNamespace,NoNamespace) [] (Just m) deps1 []
+  cm <- fmap (createLanguageModule [] em) $ loadModuleGlobals resolver path (NoNamespace,NoNamespace) [] (Just m) deps1 []
   mapCompilerM (runSingleTest backend cl cm path paths (m:deps2)) ts' where
     allowTests = Set.fromList tp
     isTestAllowed t = if null allowTests then True else t `Set.member` allowTests
@@ -316,9 +313,9 @@
   let testing = any isTestsOnly pragmas
   return $ PrivateSource ns testing cs' ds
 
-createLanguageModule :: [CategoryName] -> [CategoryName] -> ExprMap c ->
+createLanguageModule :: [CategoryName] -> ExprMap c ->
   [WithVisibility (AnyCategory c)] -> LanguageModule c
-createLanguageModule ex ss em cs = lm where
+createLanguageModule ss em cs = lm where
   lm = LanguageModule {
       lmPublicNamespaces  = Set.fromList $ map wvData $ apply ns [with    FromDependency,without ModuleOnly],
       lmPrivateNamespaces = Set.fromList $ map wvData $ apply ns [with    FromDependency,with    ModuleOnly],
@@ -329,7 +326,6 @@
       lmPublicLocal       = map wvData $ apply cs [without FromDependency,without ModuleOnly,without TestsOnly],
       lmPrivateLocal      = map wvData $ apply cs [without FromDependency,with    ModuleOnly,without TestsOnly],
       lmTestingLocal      = map wvData $ apply cs [without FromDependency,with TestsOnly],
-      lmExternal = ex,
       lmStreamlined = ss,
       lmExprMap  = em
     }
diff --git a/src/Cli/RunCompiler.hs b/src/Cli/RunCompiler.hs
--- a/src/Cli/RunCompiler.hs
+++ b/src/Cli/RunCompiler.hs
@@ -168,10 +168,14 @@
   compileSingle d = do
     d' <- errorFromIO $ canonicalizePath (p </> d)
     (is',is2') <- maybeUseConfig d'
-    base <- resolveBaseModule resolver
     as  <- fmap fixPaths $ mapCompilerM (resolveModule resolver d') is'
     as2 <- fmap fixPaths $ mapCompilerM (resolveModule resolver d') is2'
-    deps1 <- loadPublicDeps compilerHash f Map.empty (base:as)
+    isBase <- isBaseModule resolver d'
+    deps1 <- if isBase
+                then loadPublicDeps compilerHash f Map.empty as
+                else do
+                  base <- resolveBaseModule resolver
+                  loadPublicDeps compilerHash f Map.empty (base:as)
     deps2 <- loadPublicDeps compilerHash f (mapMetadata deps1) as2
     path <- errorFromIO $ canonicalizePath p
     createModuleTemplates resolver path d deps1 deps2 <?? "In module \"" ++ d' ++ "\""
diff --git a/src/Cli/TestRunner.hs b/src/Cli/TestRunner.hs
--- a/src/Cli/TestRunner.hs
+++ b/src/Cli/TestRunner.hs
@@ -145,15 +145,18 @@
            when (not $ any isCompilerError results) $ errorFromIO $ removeDirectoryRecursive dir
            return results
 
-    executeTest binary rs es res s2 (f2,c) = printOutcome $ "\nIn unittest " ++ show f2 ++ formatFullContextBrace c ??> do
+    executeTest binary rs es res s2 (f2,c) = printOutcome $ context ??> do
       let command = TestCommand binary (takeDirectory binary) [show f2,cl]
-      (TestCommandResult s2' out err) <- runTestCommand b command
-      case (s2,s2') of
-           (True,False) -> collectAllM_ $ (asCompilerError res):(map compilerErrorM $ err ++ out)
-           (False,True) -> collectAllM_ [compilerErrorM "Expected runtime failure",
-                                         asCompilerError res <?? "\nOutput from compiler:"]
-           _ -> fromTrackedErrors $ checkContent rs es (lines $ show $ getCompilerWarnings res) err out
+      resetBackgroundM $ do
+        compilerBackgroundM $ "See output files for testcase in " ++ (takeDirectory binary)
+        (TestCommandResult s2' out err) <- runTestCommand b command
+        case (s2,s2') of
+             (True,False) -> collectAllM_ $ (asCompilerError res):(map compilerErrorM $ err ++ out)
+             (False,True) -> collectAllM_ [compilerErrorM "Expected runtime failure",
+                                          asCompilerError res <?? "\nOutput from compiler:"]
+             _ -> fromTrackedErrors $ checkContent rs es (lines $ show $ getCompilerWarnings res) err out
       where
+        context = "\nIn unittest " ++ show f2 ++ formatFullContextBrace c
         printOutcome outcome = do
           failed <- isCompilerErrorM outcome
           if failed
diff --git a/src/Compilation/ProcedureContext.hs b/src/Compilation/ProcedureContext.hs
--- a/src/Compilation/ProcedureContext.hs
+++ b/src/Compilation/ProcedureContext.hs
@@ -146,8 +146,11 @@
       getFromAny _ =
         compilerErrorM $ "Use explicit type conversion to call " ++ show n ++ " from " ++ show t
       getFromAll ts = do
+        t' <- case t of
+                   Just t2 -> return t2
+                   Nothing -> fmap (singleType . JustTypeInstance) $ ccSelfType ctx
         collectFirstM ts <!!
-          "Function " ++ show n ++ " not available for type " ++ show t ++ formatFullContextBrace c
+          "Function " ++ show n ++ " not available for type " ++ show t' ++ formatFullContextBrace c
       getFromSingle t0 (JustParamName _ p) = do
         fa <- ccAllFilters ctx
         fs <- case p `Map.lookup` fa of
diff --git a/src/Compilation/ScopeContext.hs b/src/Compilation/ScopeContext.hs
--- a/src/Compilation/ScopeContext.hs
+++ b/src/Compilation/ScopeContext.hs
@@ -63,7 +63,7 @@
 
 getProcedureScopes :: (Show c, CollectErrorsM m) =>
   CategoryMap c -> ExprMap c -> DefinedCategory c -> m [ProcedureScope c]
-getProcedureScopes ta em (DefinedCategory c n pragmas _ _ ms ps fs) = do
+getProcedureScopes ta em (DefinedCategory c n pragmas _ _ ms ps fs) = message ??> do
   (_,t) <- getConcreteCategory ta (c,n)
   let params = Positional $ getCategoryParams t
   let typeInstance = TypeInstance n $ fmap (singleType . JustParamName False . vpParam) params
@@ -92,6 +92,7 @@
   let ctxV = ScopeContext ta n params vm' filters fa vv em
   return [ProcedureScope ctxC cp,ProcedureScope ctxT tp',ProcedureScope ctxV vp']
   where
+    message = "In compilation of definition for " ++ show n ++ formatFullContextBrace c
     checkPragma (MembersReadOnly c2 vs) = do
       let missing = Set.toList $ Set.fromList vs `Set.difference` allMembers
       mapCompilerM_ (\v -> compilerErrorM $ "Member " ++ show v ++
diff --git a/src/CompilerCxx/CategoryContext.hs b/src/CompilerCxx/CategoryContext.hs
--- a/src/CompilerCxx/CategoryContext.hs
+++ b/src/CompilerCxx/CategoryContext.hs
@@ -34,7 +34,7 @@
 import Compilation.CompilerState
 import Compilation.ProcedureContext
 import Compilation.ScopeContext
-import Types.Builtin
+import CompilerCxx.Code (isStoredUnboxed)
 import Types.DefinedCategory
 import Types.Procedure
 import Types.TypeCategory
@@ -116,7 +116,7 @@
   let ns0 = if isUnnamedReturns rs2
                then []
                else zipWith3 ReturnVariable [0..] (map ovName $ pValues $ nrNames rs2) (map pvType $ pValues rs1)
-  let ns = filter (isPrimitiveType . rvType) ns0
+  let ns = filter (isStoredUnboxed . rvType) ns0
   return $ ProcedureContext {
       _pcScope = s,
       _pcType = t,
diff --git a/src/CompilerCxx/Code.hs b/src/CompilerCxx/Code.hs
--- a/src/CompilerCxx/Code.hs
+++ b/src/CompilerCxx/Code.hs
@@ -25,8 +25,10 @@
   emptyCode,
   escapeChar,
   escapeChars,
+  expressionFromLiteral,
   functionLabelType,
   indentCompiled,
+  isStoredUnboxed,
   newFunctionLabel,
   noTestsOnlySourceGuard,
   onlyCode,
@@ -66,6 +68,7 @@
 import Compilation.CompilerState
 import CompilerCxx.Naming
 import Types.Builtin
+import Types.Procedure (ExpressionType)
 import Types.TypeCategory
 import Types.TypeInstance
 
@@ -115,6 +118,26 @@
 
 showCreationTrace :: String
 showCreationTrace = "TRACE_CREATION"
+
+isStoredUnboxed :: ValueType -> Bool
+isStoredUnboxed t
+  | t == boolRequiredValue  = True
+  | t == intRequiredValue   = True
+  | t == floatRequiredValue = True
+  | t == charRequiredValue  = True
+  | otherwise               = False
+
+expressionFromLiteral :: PrimitiveType -> String -> (ExpressionType,ExpressionValue)
+expressionFromLiteral PrimString e =
+  (Positional [stringRequiredValue],UnboxedPrimitive PrimString $ "PrimString_FromLiteral(" ++ e ++ ")")
+expressionFromLiteral PrimChar e =
+  (Positional [charRequiredValue],UnboxedPrimitive PrimChar $ "PrimChar(" ++ e ++ ")")
+expressionFromLiteral PrimInt e =
+  (Positional [intRequiredValue],UnboxedPrimitive PrimInt $ "PrimInt(" ++ e ++ ")")
+expressionFromLiteral PrimFloat e =
+  (Positional [floatRequiredValue],UnboxedPrimitive PrimFloat $ "PrimFloat(" ++ e ++ ")")
+expressionFromLiteral PrimBool e =
+  (Positional [boolRequiredValue],UnboxedPrimitive PrimBool e)
 
 getFromLazy :: ExpressionValue -> ExpressionValue
 getFromLazy (OpaqueMulti e)        = OpaqueMulti $ e ++ ".Get()"
diff --git a/src/CompilerCxx/CxxFiles.hs b/src/CompilerCxx/CxxFiles.hs
--- a/src/CompilerCxx/CxxFiles.hs
+++ b/src/CompilerCxx/CxxFiles.hs
@@ -140,7 +140,7 @@
     label f = "extern " ++ functionLabelType f ++ " " ++ functionName f ++ ";"
     collection
       | isValueConcrete t = emptyCode
-      | otherwise         = onlyCodes ["extern const void* const " ++ collectionName name ++ ";"]
+      | otherwise         = onlyCodes ["extern const CollectionType " ++ collectionName name ++ ";"]
     getCategory2
       | isInstanceInterface t = emptyCode
       | otherwise             = declareGetCategory t
@@ -242,7 +242,7 @@
                          (getCategoryNamespace t)
                          (Set.fromList [getCategoryNamespace t])
                          req'
-                         (addSourceIncludes $ addStreamlinedHeader t $ addIncludes req' out)
+                         (addTemplateIncludes $ addStreamlinedHeader t $ addIncludes req' out)
     filename = templateStreamlined (getCategoryName t)
     defined = DefinedCategory {
         dcContext = [],
@@ -311,7 +311,7 @@
         "namespace {",
         "const int " ++ collectionValName ++ " = 0;",
         "}  // namespace",
-        "const void* const " ++ collectionName name ++ " = &" ++ collectionValName ++ ";"
+        "const CollectionType " ++ collectionName name ++ " = COLLECTION_ID(&" ++ collectionValName ++ ");"
       ]
     createAllLabels = return $ onlyCodes $ concat $ map createLabels [cf,tf,vf]
     collectionValName = "collection_" ++ show name
@@ -597,6 +597,7 @@
     | testing   = (testsOnlySourceGuard ++)
     | otherwise = (noTestsOnlySourceGuard ++)
   addSourceIncludes = (baseSourceIncludes ++)
+  addTemplateIncludes = (templateIncludes ++)
   addCategoryHeader t = (["#include \"" ++ headerFilename (getCategoryName t) ++ "\""] ++)
   addStreamlinedHeader t = (["#include \"" ++ headerStreamlined (getCategoryName t) ++ "\""] ++)
   addIncludes req = (map (\i -> "#include \"" ++ headerFilename i ++ "\"") (Set.toList req) ++)
@@ -696,9 +697,10 @@
                               " = " ++ newFunctionLabel i f ++ ";"
 
 createFunctionDispatch :: CategoryName -> SymbolScope -> [ScopedFunction c] -> CompiledData [String]
-createFunctionDispatch n s fs = onlyCodes $ [typedef] ++
-                                            concat (map table $ byCategory) ++
-                                            metaTable ++ select where
+createFunctionDispatch n s fs = function where
+  function
+    | null filtered = onlyCode $ "{ " ++ fallback ++ " }"
+    | otherwise = onlyCodes $ [typedef] ++ concat (map table $ byCategory) ++ metaTable ++ select
   filtered = filter ((== s) . sfScope) fs
   flatten f = f:(concat $ map flatten $ sfMerges f)
   flattened = concat $ map flatten filtered
@@ -722,11 +724,11 @@
     ["  };"]
   metaTable = ["  static DispatchTable<CallType> all_tables[] = {"] ++
               map dispatchKeyValue byCategory ++
-              ["    DispatchTable<CallType>(),","  };"]
+              ["  };"]
   dispatchKeyValue (n2,_) = "    DispatchTable<CallType>(" ++ collectionName n2 ++ ", " ++ tableName n2 ++ "),"
   select = [
-      "  std::atomic_bool table_lock{0};",
-      "  const DispatchTable<CallType>* const table = DispatchSelect(table_lock, label.collection, all_tables);",
+      "  static const StaticSort force_sort = all_tables;",
+      "  const DispatchTable<CallType>* const table = DispatchSelect(label.collection, all_tables);",
       "  if (table) {",
       "    if (label.function_num < 0 || label.function_num >= table->size) {",
       "      FAIL() << \"Bad function call \" << label;",
@@ -788,10 +790,9 @@
       "  using CallType = void(" ++ className ++ "::*)(std::vector<S<const TypeInstance>>&)const;",
       "  static DispatchSingle<CallType> all_calls[] = {"
     ] ++ map dispatchKeyValue ((getCategoryName t):refines) ++ [
-      "    DispatchSingle<CallType>(),",
       "  };",
-      "  std::atomic_bool table_lock{0};",
-      "  const DispatchSingle<CallType>* const call = DispatchSelect(table_lock, &category, all_calls);",
+      "  static const StaticSort force_sort = all_calls;",
+      "  const DispatchSingle<CallType>* const call = DispatchSelect(&category, all_calls);",
       "  if (call) {",
       "    (this->*call->value)(args);",
       "    return true;",
diff --git a/src/CompilerCxx/LanguageModule.hs b/src/CompilerCxx/LanguageModule.hs
--- a/src/CompilerCxx/LanguageModule.hs
+++ b/src/CompilerCxx/LanguageModule.hs
@@ -54,7 +54,6 @@
     lmPublicLocal :: [AnyCategory c],
     lmPrivateLocal :: [AnyCategory c],
     lmTestingLocal :: [AnyCategory c],
-    lmExternal :: [CategoryName],
     lmStreamlined :: [CategoryName],
     lmExprMap :: ExprMap c
   }
@@ -69,7 +68,7 @@
 
 compileLanguageModule :: (Ord c, Show c, CollectErrorsM m) =>
   LanguageModule c -> [PrivateSource c] -> m [CxxOutput]
-compileLanguageModule (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1 ex ss em) xa = do
+compileLanguageModule (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1  ss em) xa = do
   let dm = mapDefByName $ concat $ map psDefine xa
   checkDefined dm extensions $ filter isValueConcrete (cs1 ++ ps1 ++ ts1)
   checkSupefluous $ Set.toList $ extensions `Set.difference` ca
@@ -82,13 +81,12 @@
     map (generateNativeInterface True  nsPrivate) (onlyNativeInterfaces ts1)
   xxPrivate <- fmap concat $ mapCompilerM (compilePrivate tmPrivate tmTesting) xa
   xxStreamlined <- fmap concat $ mapCompilerM (streamlined tmTesting) $ nub ss
-  xxVerbose <- fmap concat $ mapCompilerM (verbose tmTesting) $ nub ex
-  let allFiles = xxInterfaces ++ xxPrivate ++ xxStreamlined ++ xxVerbose
+  let allFiles = xxInterfaces ++ xxPrivate ++ xxStreamlined
   noDuplicateFiles $ map (\f -> (coFilename f,coNamespace f)) allFiles
   return allFiles where
     nsPublic  = ns0 `Set.union` ns2
     nsPrivate = ns1 `Set.union` nsPublic
-    extensions = Set.fromList $ ex ++ ss
+    extensions = Set.fromList ss
     testingCats = Set.fromList $ map getCategoryName ts1
     onlyNativeInterfaces = filter (not . (`Set.member` extensions) . getCategoryName) . filter (not . isValueConcrete)
     localCats = Set.fromList $ map getCategoryName $ cs1 ++ ps1 ++ ts1
@@ -96,10 +94,6 @@
       checkLocal localCats ([] :: [String]) n
       (_,t) <- getConcreteCategory tm ([],n)
       generateStreamlinedExtension (n `Set.member` testingCats) nsPrivate t
-    verbose tm n = do
-      checkLocal localCats ([] :: [String]) n
-      (_,t) <- getConcreteCategory tm ([],n)
-      generateVerboseExtension (n `Set.member` testingCats) t
     compilePrivate tmPrivate tmTesting (PrivateSource ns3 testing cs2 ds) = do
       let tm = if testing
                   then tmTesting
@@ -188,7 +182,7 @@
 compileTestMain :: (Ord c, Show c, CollectErrorsM m) =>
   LanguageModule c -> [String] -> PrivateSource c -> [TestProcedure c] ->
   m (CxxOutput,[(FunctionName,[c])])
-compileTestMain (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1 _ _ em) args ts2 tests = do
+compileTestMain (LanguageModule ns0 ns1 ns2 cs0 ps0 ts0 cs1 ps1 ts1 _ em) args ts2 tests = do
   tm' <- tm
   (CompiledData req main) <- generateTestFile tm' em args tests
   let output = CxxOutput Nothing testFilename NoNamespace (psNamespace ts2 `Set.insert` Set.unions [ns0,ns1,ns2]) req main
@@ -198,7 +192,7 @@
 
 compileModuleMain :: (Ord c, Show c, CollectErrorsM m) =>
   LanguageModule c -> [PrivateSource c] -> CategoryName -> FunctionName -> m CxxOutput
-compileModuleMain (LanguageModule ns0 ns1 ns2 cs0 ps0 _ cs1 ps1 _ _ _ em) xa n f = do
+compileModuleMain (LanguageModule ns0 ns1 ns2 cs0 ps0 _ cs1 ps1 _ _ em) xa n f = do
   let resolved = filter (\d -> dcName d == n) $ concat $ map psDefine $ filter (not . psTesting) xa
   reconcile resolved
   tm' <- tm
diff --git a/src/CompilerCxx/Naming.hs b/src/CompilerCxx/Naming.hs
--- a/src/CompilerCxx/Naming.hs
+++ b/src/CompilerCxx/Naming.hs
@@ -47,6 +47,7 @@
   sourceFilename,
   sourceStreamlined,
   tableName,
+  templateIncludes,
   templateStreamlined,
   testFilename,
   testFunctionName,
@@ -94,7 +95,10 @@
 baseHeaderIncludes = ["#include \"category-header.hpp\""]
 
 baseSourceIncludes :: [String]
-baseSourceIncludes = ["#include \"category-source.hpp\""]
+baseSourceIncludes = ["#include \"category-source.hpp\"","#include \"internal.hpp\""]
+
+templateIncludes :: [String]
+templateIncludes = ["#include \"category-source.hpp\""]
 
 mainSourceIncludes :: [String]
 mainSourceIncludes = ["#include \"logging.hpp\""]
diff --git a/src/CompilerCxx/Procedure.hs b/src/CompilerCxx/Procedure.hs
--- a/src/CompilerCxx/Procedure.hs
+++ b/src/CompilerCxx/Procedure.hs
@@ -158,7 +158,7 @@
       | isUnnamedReturns rs2 = []
       | otherwise = map (\(i,(t2,n2)) -> nameReturn i (pvType t2) n2) (zip ([0..] :: [Int]) $ zip (pValues rs1) (pValues $ nrNames rs2))
     nameReturn i t2 n2
-      | isPrimitiveType t2 = variableProxyType t2 ++ " " ++ variableName (ovName n2) ++ ";"
+      | isStoredUnboxed t2 = variableProxyType t2 ++ " " ++ variableName (ovName n2) ++ ";"
       | otherwise =
         variableProxyType t2 ++ " " ++ variableName (ovName n2) ++
         " = " ++ writeStoredVariable t2 (UnwrappedSingle $ "returns.At(" ++ show i ++ ")") ++ ";"
@@ -537,33 +537,33 @@
 compileExpression = compile where
   compile (Literal (StringLiteral _ l)) = do
     csAddRequired (Set.fromList [BuiltinString])
-    return (Positional [stringRequiredValue],UnboxedPrimitive PrimString $ "PrimString_FromLiteral(" ++ escapeChars l ++ ")")
+    return $ expressionFromLiteral PrimString (escapeChars l)
   compile (Literal (CharLiteral _ l)) = do
     csAddRequired (Set.fromList [BuiltinChar])
-    return (Positional [charRequiredValue],UnboxedPrimitive PrimChar $ "PrimChar('" ++ escapeChar l ++ "')")
+    return $ expressionFromLiteral PrimChar ("'" ++ escapeChar l ++ "'")
   compile (Literal (IntegerLiteral c True l)) = do
     csAddRequired (Set.fromList [BuiltinInt])
     when (l > 2^(64 :: Integer) - 1) $ compilerErrorM $
       "Literal " ++ show l ++ formatFullContextBrace c ++ " is greater than the max value for 64-bit unsigned"
     let l' = if l > 2^(63 :: Integer) - 1 then l - 2^(64 :: Integer) else l
-    return (Positional [intRequiredValue],UnboxedPrimitive PrimInt $ "PrimInt(" ++ show l' ++ ")")
+    return $ expressionFromLiteral PrimInt (show l')
   compile (Literal (IntegerLiteral c False l)) = do
     csAddRequired (Set.fromList [BuiltinInt])
     when (l > 2^(63 :: Integer) - 1) $ compilerErrorM $
       "Literal " ++ show l ++ formatFullContextBrace c ++ " is greater than the max value for 64-bit signed"
     when ((-l) > (2^(63 :: Integer) - 2)) $ compilerErrorM $
       "Literal " ++ show l ++ formatFullContextBrace c ++ " is less than the min value for 64-bit signed"
-    return (Positional [intRequiredValue],UnboxedPrimitive PrimInt $ "PrimInt(" ++ show l ++ ")")
+    return $ expressionFromLiteral PrimInt (show l)
   compile (Literal (DecimalLiteral _ l e)) = do
     csAddRequired (Set.fromList [BuiltinFloat])
     -- TODO: Check bounds.
-    return (Positional [floatRequiredValue],UnboxedPrimitive PrimFloat $ "PrimFloat(" ++ show l ++ "E" ++ show e ++ ")")
+    return $ expressionFromLiteral PrimFloat (show l ++ "E" ++ show e)
   compile (Literal (BoolLiteral _ True)) = do
     csAddRequired (Set.fromList [BuiltinBool])
-    return (Positional [boolRequiredValue],UnboxedPrimitive PrimBool "true")
+    return $ expressionFromLiteral PrimBool "true"
   compile (Literal (BoolLiteral _ False)) = do
     csAddRequired (Set.fromList [BuiltinBool])
-    return (Positional [boolRequiredValue],UnboxedPrimitive PrimBool "false")
+    return $ expressionFromLiteral PrimBool "false"
   compile (Literal (EmptyLiteral _)) = do
     return (Positional [emptyType],UnwrappedSingle "Var_empty")
   compile (Expression _ s os) = do
diff --git a/src/Parser/TypeInstance.hs b/src/Parser/TypeInstance.hs
--- a/src/Parser/TypeInstance.hs
+++ b/src/Parser/TypeInstance.hs
@@ -75,12 +75,13 @@
     return $ box (b:e)
     where
       box n
-        | n == "Bool"      = BuiltinBool
-        | n == "Char"      = BuiltinChar
-        | n == "Int"       = BuiltinInt
-        | n == "Float"     = BuiltinFloat
-        | n == "String"    = BuiltinString
-        | n == "Formatted" = BuiltinFormatted
+        | n == "Bool"       = BuiltinBool
+        | n == "Char"       = BuiltinChar
+        | n == "CharBuffer" = BuiltinCharBuffer
+        | n == "Int"        = BuiltinInt
+        | n == "Float"      = BuiltinFloat
+        | n == "String"     = BuiltinString
+        | n == "Formatted"  = BuiltinFormatted
         | otherwise = CategoryName n
 
 instance ParseFromSource ParamName where
diff --git a/src/Types/Builtin.hs b/src/Types/Builtin.hs
--- a/src/Types/Builtin.hs
+++ b/src/Types/Builtin.hs
@@ -29,7 +29,6 @@
   floatRequiredValue,
   formattedRequiredValue,
   intRequiredValue,
-  isPrimitiveType,
   orderOptionalValue,
   stringRequiredValue,
 ) where
@@ -69,19 +68,11 @@
 
 data PrimitiveType =
   PrimBool |
-  PrimString |
   PrimChar |
   PrimInt |
-  PrimFloat
+  PrimFloat |
+  PrimString
   deriving (Eq,Show)
-
-isPrimitiveType :: ValueType -> Bool
-isPrimitiveType t
-  | t == boolRequiredValue  = True
-  | t == intRequiredValue   = True
-  | t == floatRequiredValue = True
-  | t == charRequiredValue  = True
-  | otherwise               = False
 
 data ExpressionValue =
   -- Multi argument/return tuple.
diff --git a/src/Types/Procedure.hs b/src/Types/Procedure.hs
--- a/src/Types/Procedure.hs
+++ b/src/Types/Procedure.hs
@@ -53,7 +53,6 @@
   getStatementContext,
   isDiscardedInput,
   isFunctionOperator,
-  isLiteralCategory,
   isNoTrace,
   isTraceCreation,
   isRawCodeLine,
@@ -250,8 +249,11 @@
 data InstanceOrInferred c =
   AssignedInstance [c] GeneralInstance |
   InferredInstance [c]
-  deriving (Show)
 
+instance Show c => Show (InstanceOrInferred c) where
+  show (AssignedInstance _ t) = show t
+  show (InferredInstance _)   = "?"
+
 data FunctionSpec c =
   FunctionSpec [c] (FunctionQualifier c) FunctionName (Positional (InstanceOrInferred c))
   deriving (Show)
@@ -312,14 +314,6 @@
 getValueLiteralContext (DecimalLiteral c _ _) = c
 getValueLiteralContext (BoolLiteral c _)      = c
 getValueLiteralContext (EmptyLiteral c)       = c
-
-isLiteralCategory :: CategoryName -> Bool
-isLiteralCategory BuiltinBool   = True
-isLiteralCategory BuiltinChar   = True
-isLiteralCategory BuiltinInt    = True
-isLiteralCategory BuiltinFloat  = True
-isLiteralCategory BuiltinString = True
-isLiteralCategory _             = False
 
 data ValueOperation c =
   ConvertedCall [c] TypeInstance (FunctionCall c) |
diff --git a/src/Types/TypeInstance.hs b/src/Types/TypeInstance.hs
--- a/src/Types/TypeInstance.hs
+++ b/src/Types/TypeInstance.hs
@@ -133,6 +133,7 @@
   } |
   BuiltinBool |
   BuiltinChar |
+  BuiltinCharBuffer |
   BuiltinInt |
   BuiltinFloat |
   BuiltinString |
@@ -144,6 +145,7 @@
   show (CategoryName n)    = n
   show BuiltinBool         = "Bool"
   show BuiltinChar         = "Char"
+  show BuiltinCharBuffer   = "CharBuffer"
   show BuiltinInt          = "Int"
   show BuiltinFloat        = "Float"
   show BuiltinString       = "String"
diff --git a/tests/builtin-types.0rt b/tests/builtin-types.0rt
--- a/tests/builtin-types.0rt
+++ b/tests/builtin-types.0rt
@@ -191,6 +191,41 @@
 }
 
 
+testcase "reduce CharBuffer" {
+  success
+}
+
+unittest charBuffer {
+  if (!present(reduce<CharBuffer,CharBuffer>(CharBuffer.new(0)))) {
+    fail("Failed")
+  }
+}
+
+unittest readAt {
+  if (!present(reduce<CharBuffer,ReadAt<Char>>(CharBuffer.new(0)))) {
+    fail("Failed")
+  }
+  if (present(reduce<CharBuffer,ReadAt<Int>>(CharBuffer.new(0)))) {
+    fail("Failed")
+  }
+}
+
+unittest writeAt {
+  if (!present(reduce<CharBuffer,WriteAt<Char>>(CharBuffer.new(0)))) {
+    fail("Failed")
+  }
+  if (present(reduce<CharBuffer,WriteAt<Int>>(CharBuffer.new(0)))) {
+    fail("Failed")
+  }
+}
+
+unittest container {
+  if (!present(reduce<CharBuffer,Container>(CharBuffer.new(0)))) {
+    fail("Failed")
+  }
+}
+
+
 testcase "reduce String" {
   success
 }
@@ -235,6 +270,9 @@
   if (!present(reduce<String,DefaultOrder<Char>>("a"))) {
     fail("Failed")
   }
+  if (present(reduce<String,DefaultOrder<Int>>("a"))) {
+    fail("Failed")
+  }
 }
 
 unittest subSequence {
@@ -243,7 +281,22 @@
   }
 }
 
+unittest readAt {
+  if (!present(reduce<String,ReadAt<Char>>("a"))) {
+    fail("Failed")
+  }
+  if (present(reduce<String,ReadAt<Int>>("a"))) {
+    fail("Failed")
+  }
+}
 
+unittest container {
+  if (!present(reduce<String,Container>("a"))) {
+    fail("Failed")
+  }
+}
+
+
 testcase "interface implementations" {
   success
 }
@@ -432,6 +485,108 @@
 }
 
 
+testcase "CharBuffer stuff" {
+  success
+}
+
+unittest writeAndRead {
+  CharBuffer buffer <- CharBuffer.new(5)
+      .writeAt(0,'a')
+      .writeAt(1,'b')
+      .writeAt(2,'c')
+      .writeAt(3,'d')
+      .writeAt(4,'e')
+      // overwriting here
+      .writeAt(1,'B')
+      .writeAt(2,'C')
+
+  \ Testing.checkEquals<?>(buffer.size(),5)
+  \ Testing.checkEquals<?>(buffer.readAt(0),'a')
+  \ Testing.checkEquals<?>(buffer.readAt(1),'B')
+  \ Testing.checkEquals<?>(buffer.readAt(2),'C')
+  \ Testing.checkEquals<?>(buffer.readAt(3),'d')
+  \ Testing.checkEquals<?>(buffer.readAt(4),'e')
+}
+
+unittest resizeDown {
+  CharBuffer buffer <- CharBuffer.new(5).resize(3).writeAt(2,'a')
+  \ Testing.checkEquals<?>(buffer.size(),3)
+  \ Testing.checkEquals<?>(buffer.readAt(2),'a')
+}
+
+unittest resizeUp {
+  CharBuffer buffer <- CharBuffer.new(5).resize(7).writeAt(6,'a')
+  \ Testing.checkEquals<?>(buffer.size(),7)
+  \ Testing.checkEquals<?>(buffer.readAt(6),'a')
+}
+
+
+testcase "CharBuffer read negative" {
+  crash
+  require "-10"
+  require "bounds"
+}
+
+unittest test {
+  \ CharBuffer.new(3).readAt(-10)
+}
+
+
+testcase "CharBuffer read past end" {
+  crash
+  require "3"
+  require "bounds"
+}
+
+unittest test {
+  \ CharBuffer.new(3).readAt(3)
+}
+
+
+testcase "CharBuffer write negative" {
+  crash
+  require "-10"
+  require "bounds"
+}
+
+unittest test {
+  \ CharBuffer.new(3).writeAt(-10,'a')
+}
+
+
+testcase "CharBuffer write past end" {
+  crash
+  require "3"
+  require "bounds"
+}
+
+unittest test {
+  \ CharBuffer.new(3).writeAt(3,'a')
+}
+
+
+testcase "CharBuffer new negative" {
+  crash
+  require "-10"
+  require "size"
+}
+
+unittest test {
+  \ CharBuffer.new(-10)
+}
+
+
+testcase "CharBuffer resize negative" {
+  crash
+  require "-10"
+  require "size"
+}
+
+unittest test {
+  \ CharBuffer.new(0).resize(-10)
+}
+
+
 testcase "String stuff" {
   success
 }
@@ -498,7 +653,17 @@
   \ Testing.checkEquals<?>(index,7)
 }
 
+unittest fromCharBuffer {
+  CharBuffer buffer <- CharBuffer.new(5)
+      .writeAt(0,'a')
+      .writeAt(1,'b')
+      .writeAt(2,'c')
+      .writeAt(3,'d')
+      .writeAt(4,'e')
+  \ Testing.checkEquals<?>(String.fromCharBuffer(buffer),"abcde")
+}
 
+
 testcase "String access negative" {
   crash
   require "-10"
@@ -522,7 +687,7 @@
 
 testcase "String access past end" {
   crash
-  require "100"
+  require "3"
   require "bounds"
 }
 
@@ -532,7 +697,7 @@
 
 define Test {
   run () {
-    \ ("abc").readAt(100)
+    \ ("abc").readAt(3)
   }
 }
 
@@ -543,7 +708,7 @@
 
 testcase "String subsequence past end" {
   crash
-  require "100"
+  require "3"
   require "size"
 }
 
@@ -553,7 +718,7 @@
 
 define Test {
   run () {
-    \ ("abc").subSequence(1,100)
+    \ ("abc").subSequence(1,3)
   }
 }
 
diff --git a/tests/function-calls.0rt b/tests/function-calls.0rt
--- a/tests/function-calls.0rt
+++ b/tests/function-calls.0rt
@@ -447,3 +447,118 @@
 }
 
 concrete Test {}
+
+
+testcase "large dispatch tables" {
+  success
+}
+
+unittest callDispatch {
+  \ Testing.checkEquals<?>(Type.new().get(),00)
+  \ Testing.checkEquals<?>(Type.new().get01(),01)
+  \ Testing.checkEquals<?>(Type.new().get02(),02)
+  \ Testing.checkEquals<?>(Type.new().get03(),03)
+  \ Testing.checkEquals<?>(Type.new().get04(),04)
+  \ Testing.checkEquals<?>(Type.new().get05(),05)
+  \ Testing.checkEquals<?>(Type.new().get06(),06)
+  \ Testing.checkEquals<?>(Type.new().get07(),07)
+  \ Testing.checkEquals<?>(Type.new().get08(),08)
+  \ Testing.checkEquals<?>(Type.new().get09(),09)
+  \ Testing.checkEquals<?>(Type.new().get10(),10)
+  \ Testing.checkEquals<?>(Type.new().get11(),11)
+  \ Testing.checkEquals<?>(Type.new().get12(),12)
+  \ Testing.checkEquals<?>(Type.new().get13(),13)
+  \ Testing.checkEquals<?>(Type.new().get14(),14)
+  \ Testing.checkEquals<?>(Type.new().get15(),15)
+  \ Testing.checkEquals<?>(Type.new().get16(),16)
+  \ Testing.checkEquals<?>(Type.new().get17(),17)
+  \ Testing.checkEquals<?>(Type.new().get18(),18)
+}
+
+unittest reduceBuiltin {
+  \ Testing.checkPresent(reduce<Type,Type>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base01>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base02>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base03>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base04>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base05>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base06>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base07>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base08>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base09>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base10>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base11>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base12>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base13>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base14>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base15>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base16>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base17>(Type.new()))
+  \ Testing.checkPresent(reduce<Type,Base18>(Type.new()))
+}
+
+concrete Type {
+  refines Base01
+  refines Base02
+  refines Base03
+  refines Base04
+  refines Base05
+  refines Base06
+  refines Base07
+  refines Base08
+  refines Base09
+  refines Base10
+  refines Base11
+  refines Base12
+  refines Base13
+  refines Base14
+  refines Base15
+  refines Base16
+  refines Base17
+  refines Base18
+
+  @type new () -> (Type)
+  @value get () -> (Int)
+}
+
+define Type {
+  new () { return Type{ } }
+  get () { return 00 }
+  get01 () { return 01 }
+  get02 () { return 02 }
+  get03 () { return 03 }
+  get04 () { return 04 }
+  get05 () { return 05 }
+  get06 () { return 06 }
+  get07 () { return 07 }
+  get08 () { return 08 }
+  get09 () { return 09 }
+  get10 () { return 10 }
+  get11 () { return 11 }
+  get12 () { return 12 }
+  get13 () { return 13 }
+  get14 () { return 14 }
+  get15 () { return 15 }
+  get16 () { return 16 }
+  get17 () { return 17 }
+  get18 () { return 18 }
+}
+
+@value interface Base01 { get01 () -> (Int) }
+@value interface Base02 { get02 () -> (Int) }
+@value interface Base03 { get03 () -> (Int) }
+@value interface Base04 { get04 () -> (Int) }
+@value interface Base05 { get05 () -> (Int) }
+@value interface Base06 { get06 () -> (Int) }
+@value interface Base07 { get07 () -> (Int) }
+@value interface Base08 { get08 () -> (Int) }
+@value interface Base09 { get09 () -> (Int) }
+@value interface Base10 { get10 () -> (Int) }
+@value interface Base11 { get11 () -> (Int) }
+@value interface Base12 { get12 () -> (Int) }
+@value interface Base13 { get13 () -> (Int) }
+@value interface Base14 { get14 () -> (Int) }
+@value interface Base15 { get15 () -> (Int) }
+@value interface Base16 { get16 () -> (Int) }
+@value interface Base17 { get17 () -> (Int) }
+@value interface Base18 { get18 () -> (Int) }
diff --git a/tests/regressions.0rt b/tests/regressions.0rt
--- a/tests/regressions.0rt
+++ b/tests/regressions.0rt
@@ -86,6 +86,7 @@
   call2 (_) {}
 }
 
+
 testcase "Issue #73 is not re-broken by #self" {
   // https://github.com/ta0kira/zeolite/issues/73
   success
diff --git a/zeolite-lang.cabal b/zeolite-lang.cabal
--- a/zeolite-lang.cabal
+++ b/zeolite-lang.cabal
@@ -1,7 +1,7 @@
 cabal-version:       2.2
 
 name:                zeolite-lang
-version:             0.16.1.0
+version:             0.17.0.0
 synopsis:            Zeolite is a statically-typed, general-purpose programming language.
 
 description:
@@ -68,10 +68,10 @@
 
 data-files:          base/.zeolite-module,
                      base/*.0rp,
-                     base/*.cc,
-                     base/*.cpp,
                      base/*.hpp,
                      base/*.h,
+                     base/src/*.cc,
+                     base/src/*.cpp,
                      example/hello/README.md,
                      example/hello/*.0rx,
                      example/parser/.zeolite-module,
@@ -99,6 +99,7 @@
                      lib/math/README.md,
                      lib/math/.zeolite-module,
                      lib/math/*.0rp,
+                     lib/math/*.0rx,
                      lib/math/*.0rt,
                      lib/math/src/*.cpp,
                      lib/testing/README.md,
