diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,178 @@
 # Revision history for zeolite-lang
 
+## 0.22.0.0  -- 2021-12-11
+
+### Libraries
+
+* **[breaking]** Revises basic input and output provided by `lib/util`:
+
+  - **[breaking]** Renames `SimpleInput` to `BasicInput`.
+
+  - **[breaking]** Renames `SimpleOutput` to `BasicOutput`.
+
+  - **[breaking]** Removes `LazyStream`. To construct and write output, there
+    are a few options:
+
+    ```text
+    // Incrementally.
+    \ BasicOutput.stderr()
+        .write(someValue)
+        .write("\n")
+        .flush()
+    ```
+
+    ```text
+    // Preformatted.
+    \ BasicOutput.stderr().writeNow(String.builder()
+        .append(someValue)
+        .append("\n")
+        .build())
+    ```
+
+* **[breaking]** Renames `SearchTree` to `SortedMap` in `lib/container`.
+
+* **[breaking]** Renames `TreeSet` to `SortedSet` in `lib/container`.
+
+* **[breaking]** `Vector` changes (`lib/container`):
+
+  * **[breaking]** Replaces `Vector:create<#x>()` with `Vector<#x>.new()` for
+    consistency with other `@value` construction.
+
+  * **[breaking]** Removes `Vector:copyFrom` since it was too specific.
+
+* **[breaking]** Removes `set` and `remove` from `AutoBinaryTree`, and adds the
+  `swap` function, which can handle both `set` and `remove` operations.
+
+* **[breaking]** Removes `void` from `Void` in `lib/util`. Use `Void.default()`
+  instead.
+
+* **[new]** New categories:
+
+  * **[new]** Adds the `HashedMap` and `HashedSet` containers to
+    `lib/container`.
+
+  * **[new]** Adds `CategoricalReader`, `RandomCategorical`, and
+    `GenerateConstant` to `lib/math`.
+
+  * **[new]** Adds `FormattedH:try` to `lib/util` for formatting values during
+    debugging and logging.
+
+  * **[new]** Adds the `LessThan2` `@value interface` to `lib/util`.
+
+  * **[new]** Adds the `KVExchange`, `KeyOrder`, and `ValueOrder`
+    `@value interface`s in `lib/container` for additional map operations.
+
+* **[new]** New functionality for existing categories:
+
+  * **[new]** Implements the new `KVExchange`, `KeyOrder`, and `ValueOrder`
+    `interface`s for `SortedMap` (previously `SearchTree`) in `lib/container`.
+
+  * **[new]** Adds `copyTo` and `duplicateTo` to `OrderH` and `ReadAtH` in
+    `lib/util` for general container duplication.
+
+  * **[new]** Adds `forwardOrder`, `reverseOrder`, and `iterateWith` to
+    `ReadAtH` in `lib/util` for iterating over `ReadAt` containers.
+
+  * **[new]** Implements `Default` for containers in `lib/container` and
+    `lib/math`.
+
+  * **[new]** Implements `Default`, `Duplicate`, and `Hashed` for `Void` in
+    `lib/util`.
+
+  * **[new]** Implements `Sorting` functions in `lib/container` that use  the
+    new `LessThan2` `interface`.
+
+  * **[new]** Implements `Append<Formatted>` for `BasicOutput` (previously
+    `SimpleOutput`) in `lib/util`.
+
+  * **[new]** Adds `permuteFromWeight` to `Randomize` in `lib/math`.
+
+  * **[new]** Adds `probability` to `RandomUniform` in `lib/math`.
+
+  * **[new]** Adds `checkNotEquals` to `Testing` in `lib/testing`.
+
+### Language
+
+* **[breaking]** Makes the `Duplicate` `@value interface` from `lib/util` a
+  builtin and implements it for `Bool`, `Char`, `Float`, `Int`, and `String`.
+
+* **[breaking]** Adds the `Hashed` builtin `@value interface` and implements it
+  for `Bool`, `Char`, `Float`, `Int`, and `String`.
+
+* **[breaking]** Replaces `@value` conversion for function calls (e.g.,
+  `foo.Base.call()` to choose `call` from `Base`) with general type conversions
+  using `value?Type` syntax.
+
+  ```text
+  // Explicitly converts foo to Base.
+  \ foo?Base.call()
+  ```
+
+  The primary use-case is still to convert a union or interesection value to a
+  specific type to resolve an ambiguity in function selection. Conversions can
+  also be used to influence type inference in function calls by changing the
+  type of a passed argument.
+
+  ```text
+  // Convert the argument when using type inference.
+  \ call<?>(foo?[Container&ReadAt<#x>])
+  \ call<?>(foo?any)
+  ```
+
+  This can also be used to convert the underlying types of `optional` and `weak`
+  values, while still preserving the `optional`/`weak` storage modifier.
+
+* **[fix]** Fixes checking of `defer` in `in` blocks of `scoped`. Previously, a
+  `defer`red variable was only marked as set if the `cleanup` set it, even if
+  all paths through the `in` actually set it.
+
+  ```text
+  Int value <- defer
+  scoped {
+    // ...
+  } in if (true) {
+    value <- 1
+  } else {
+    value <- 2
+  }
+  // Previously, value appeared to be unintialized here.
+  \ foo(value)
+  ```
+
+* **[fix]** Fixes a bug in type checking of conversions to a parent
+  `@value interface` when the `refines` contains `#self`.
+
+  ```text
+  concrete Foo {
+    // Previously, there would have been an error converting Foo -> Bar<Foo>.
+    refines Bar<#self>
+  }
+  ```
+
+* **[fix]** Fixes bug in validation of internal `@category` functions with a
+  param that has the same name as a category param, where the `@category`
+  function has a filter that the category does not have.
+
+* **[new]** Allows `present`, `require`, `strong`, and `reduce<#x,#y>` builtin
+  functions to have unary notation, e.g., ``y <- `require` x``.
+
+* **[new]** Compiler now does more work attempting to resolve function calls for
+  `@value`s with union types. Previously, explicit type conversions were always
+  required to make calls from unions.
+
+* **[new]** Allows `requires` and `allows` filters to use unions and
+  intersections on the right side, e.g., `#x allows [Foo&Bar]`.
+
+* **[new]** Allows `defer` to be used with `@value` and `@category` members.
+  This is primarily because the restriction was somewhat arbitrary.
+
+* **[new]** Implements `LessThan` for `Bool`.
+
+### Compiler CLI
+
+* **[fix]** Makes `PARAM_SELF` work when used in `@value` implementations in C++
+  extensions.
+
 ## 0.21.0.0  -- 2021-11-19
 
 ### Compiler CLI
diff --git a/base/builtin.0rp b/base/builtin.0rp
--- a/base/builtin.0rp
+++ b/base/builtin.0rp
@@ -29,10 +29,13 @@
   refines AsBool
   refines AsInt
   refines AsFloat
+  refines Duplicate
   refines Formatted
+  refines Hashed
 
   defines Default
   defines Equals<Bool>
+  defines LessThan<Bool>
 }
 
 // Built-in 8-bit character type.
@@ -55,7 +58,9 @@
   refines AsChar
   refines AsInt
   refines AsFloat
+  refines Duplicate
   refines Formatted
+  refines Hashed
 
   defines Bounded
   defines Default
@@ -77,7 +82,9 @@
   refines AsBool
   refines AsInt
   refines AsFloat
+  refines Duplicate
   refines Formatted
+  refines Hashed
 
   defines Default
   defines Equals<Float>
@@ -108,7 +115,9 @@
   refines AsChar
   refines AsInt
   refines AsFloat
+  refines Duplicate
   refines Formatted
+  refines Hashed
 
   defines Bounded
   defines Default
@@ -128,7 +137,9 @@
 
   refines AsBool
   refines DefaultOrder<Char>
+  refines Duplicate
   refines Formatted
+  refines Hashed
   refines ReadAt<Char>
   refines SubSequence
 
@@ -258,10 +269,33 @@
   defaultOrder () -> (optional Order<#x>)
 }
 
+// Can be hashed.
+//
+// Notes:
+// - This interface is primarily intended for use with containers.
+// - The returned hash does not need to match across different executions of the
+//  same program.
+@value interface Hashed {
+  // Hashes the object.
+  hashed () -> (Int)
+}
+
 // Contains a flexible number of values of some type.
 @value interface Container {
   // Return the number of values.
   size () -> (Int)
+}
+
+// Can be duplicated.
+@value interface Duplicate {
+  // Creates a duplicate that can be mutated without changing the original.
+  //
+  // Notes:
+  // - This does not need to be a deep copy. The only requirement is that
+  //   mutating functions can be called on the copy without counterintuitive
+  //   results. For example, if the object is a container, the duplicate could
+  //   be a copy of the structure, while still referring to the same objects.
+  duplicate () -> (#self)
 }
 
 // Random-access reading from a container.
diff --git a/base/category-source.hpp b/base/category-source.hpp
--- a/base/category-source.hpp
+++ b/base/category-source.hpp
@@ -44,7 +44,7 @@
 
 #define VAR_SELF TypeValue::Var_self(this)
 
-#define PARAM_SELF shared_from_this()
+#define PARAM_SELF Param_self()
 
 BoxedValue Box_Bool(PrimBool value);
 BoxedValue Box_String(const PrimString& value);
diff --git a/base/function.hpp b/base/function.hpp
--- a/base/function.hpp
+++ b/base/function.hpp
@@ -84,9 +84,9 @@
   template<int Pn, int An>
   void Init() {}
 
-  template<int Pn, int An, class... Ps>
-  void Init(const S<const TypeInstance>& param, const Ps&... passed) {
-    params[Pn] = &param;
+  template<int Pn, int An, class T, class... Ps>
+  void Init(const S<const T>& param, const Ps&... passed) {
+    params[Pn] = param;
     Init<Pn+1, An>(passed...);
   }
 
@@ -102,7 +102,7 @@
     if (pos < 0 || pos >= P) {
       FAIL() << "Bad param index";
     }
-    return *params[pos];
+    return params[pos];
   }
 
   int NumArgs() const final { return A; }
@@ -114,8 +114,8 @@
     return *args[pos];
   }
 
-  const S<const TypeInstance>* params[P];
-  const BoxedValue*   args[A];
+  S<const TypeInstance> params[P];
+  const BoxedValue* args[A];
 };
 
 template<>
@@ -144,9 +144,9 @@
     Init<0>(passed...);
   }
 
-  template<int Pn, class... Ps>
-  void Init(const S<const TypeInstance>& param, const Ps&... passed) {
-    params[Pn] = &param;
+  template<int Pn, class T, class... Ps>
+  void Init(const S<const T>& param, const Ps&... passed) {
+    params[Pn] = param;
     Init<Pn+1>(passed...);
   }
 
@@ -161,13 +161,13 @@
     if (pos < 0 || pos >= P) {
       FAIL() << "Bad param index";
     }
-    return *params[pos];
+    return params[pos];
   }
 
   int NumArgs() const final                     { return returns->Size(); }
   const BoxedValue& GetArg(int pos) const final { return returns->At(pos); }
 
-  const S<const TypeInstance>* params[P];
+  S<const TypeInstance> params[P];
   const ReturnTuple* returns;
 };
 
@@ -194,8 +194,8 @@
   using Type = PassReturns<P>;
 };
 
-template<int P, class... Ps>
-struct AutoCall<P, S<const TypeInstance>, Ps...> {
+template<int P, class T, class... Ps>
+struct AutoCall<P, S<const T>, Ps...> {
   using Type = typename AutoCall<P+1, Ps...>::Type;
 };
 
diff --git a/base/src/Extension_Bool.cpp b/base/src/Extension_Bool.cpp
--- a/base/src/Extension_Bool.cpp
+++ b/base/src/Extension_Bool.cpp
@@ -23,9 +23,11 @@
 #include "Category_AsInt.hpp"
 #include "Category_Bool.hpp"
 #include "Category_Default.hpp"
+#include "Category_Duplicate.hpp"
 #include "Category_Equals.hpp"
 #include "Category_Float.hpp"
 #include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
 #include "Category_Int.hpp"
 #include "Category_String.hpp"
 
@@ -50,6 +52,13 @@
     const PrimBool Var_arg2 = (params_args.GetArg(1)).AsBool();
     return ReturnTuple(Box_Bool(Var_arg1==Var_arg2));
   }
+
+  ReturnTuple Call_lessThan(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Bool.lessThan")
+    const PrimBool Var_arg1 = (params_args.GetArg(0)).AsBool();
+    const PrimBool Var_arg2 = (params_args.GetArg(1)).AsBool();
+    return ReturnTuple(Box_Bool(!Var_arg1 && Var_arg2));
+  }
 };
 
 ReturnTuple DispatchBool(PrimBool value, const ValueFunction& label,
@@ -61,8 +70,12 @@
       return ReturnTuple(Box_Float(value ? 1.0 : 0.0));
     case CategoryId_AsInt:
       return ReturnTuple(Box_Int(value? 1 : 0));
+    case CategoryId_Duplicate:
+      return ReturnTuple(Box_Bool(value));
     case CategoryId_Formatted:
       return ReturnTuple(Box_String(value? "true" : "false"));
+    case CategoryId_Hashed:
+      return ReturnTuple(Box_Int(value ? 1000000009ULL : 1000000007ULL));
     default:
       FAIL() << "Bool does not implement " << label;
       __builtin_unreachable();
diff --git a/base/src/Extension_Char.cpp b/base/src/Extension_Char.cpp
--- a/base/src/Extension_Char.cpp
+++ b/base/src/Extension_Char.cpp
@@ -25,9 +25,11 @@
 #include "Category_Bool.hpp"
 #include "Category_Char.hpp"
 #include "Category_Default.hpp"
+#include "Category_Duplicate.hpp"
 #include "Category_Equals.hpp"
 #include "Category_Float.hpp"
 #include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
 #include "Category_Int.hpp"
 #include "Category_LessThan.hpp"
 #include "Category_String.hpp"
@@ -83,8 +85,12 @@
       return ReturnTuple(Box_Float(((int) value + 256) % 256));
     case CategoryId_AsInt:
       return ReturnTuple(Box_Int(((int) value + 256) % 256));
+    case CategoryId_Duplicate:
+      return ReturnTuple(Box_Char(value));
     case CategoryId_Formatted:
       return ReturnTuple(Box_String(PrimString(1, value)));
+    case CategoryId_Hashed:
+      return ReturnTuple(Box_Int(1000000009ULL * ((unsigned long long) value + 1000000007ULL)));
     default:
       FAIL() << "Char does not implement " << label;
       __builtin_unreachable();
diff --git a/base/src/Extension_Float.cpp b/base/src/Extension_Float.cpp
--- a/base/src/Extension_Float.cpp
+++ b/base/src/Extension_Float.cpp
@@ -25,9 +25,11 @@
 #include "Category_AsInt.hpp"
 #include "Category_Bool.hpp"
 #include "Category_Default.hpp"
+#include "Category_Duplicate.hpp"
 #include "Category_Equals.hpp"
 #include "Category_Float.hpp"
 #include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
 #include "Category_Int.hpp"
 #include "Category_LessThan.hpp"
 #include "Category_String.hpp"
@@ -71,12 +73,16 @@
       return ReturnTuple(Box_Float(value));
     case CategoryId_AsInt:
       return ReturnTuple(Box_Int(value));
+    case CategoryId_Duplicate:
+      return ReturnTuple(Box_Float(value));
     case CategoryId_Formatted: {
       // NOTE: std::to_string does weird things with significant digits.
       std::ostringstream output;
       output << value;
       return ReturnTuple(Box_String(output.str()));
     }
+    case CategoryId_Hashed:
+      return ReturnTuple(Box_Int(1000000009ULL * reinterpret_cast<uint64_t&>(value)));
     default:
       FAIL() << "Float does not implement " << label;
       __builtin_unreachable();
diff --git a/base/src/Extension_Int.cpp b/base/src/Extension_Int.cpp
--- a/base/src/Extension_Int.cpp
+++ b/base/src/Extension_Int.cpp
@@ -28,9 +28,11 @@
 #include "Category_Bool.hpp"
 #include "Category_Char.hpp"
 #include "Category_Default.hpp"
+#include "Category_Duplicate.hpp"
 #include "Category_Equals.hpp"
 #include "Category_Float.hpp"
 #include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
 #include "Category_Int.hpp"
 #include "Category_LessThan.hpp"
 #include "Category_String.hpp"
@@ -86,8 +88,12 @@
       return ReturnTuple(Box_Float(value));
     case CategoryId_AsInt:
       return ReturnTuple(Box_Int(value));
+    case CategoryId_Duplicate:
+      return ReturnTuple(Box_Int(value));
     case CategoryId_Formatted:
       return ReturnTuple(Box_String(std::to_string(value)));
+    case CategoryId_Hashed:
+      return ReturnTuple(Box_Int(1000000007ULL * value));
     default:
       FAIL() << "Int does not implement " << label;
       __builtin_unreachable();
diff --git a/base/src/Extension_String.cpp b/base/src/Extension_String.cpp
--- a/base/src/Extension_String.cpp
+++ b/base/src/Extension_String.cpp
@@ -27,8 +27,10 @@
 #include "Category_Container.hpp"
 #include "Category_Default.hpp"
 #include "Category_DefaultOrder.hpp"
+#include "Category_Duplicate.hpp"
 #include "Category_Equals.hpp"
 #include "Category_Formatted.hpp"
+#include "Category_Hashed.hpp"
 #include "Category_Int.hpp"
 #include "Category_LessThan.hpp"
 #include "Category_Order.hpp"
@@ -153,9 +155,23 @@
     }
   }
 
+  ReturnTuple Call_duplicate(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.duplicate")
+    return ReturnTuple(VAR_SELF);
+  }
+
   ReturnTuple Call_formatted(const ParamsArgs& params_args) const final {
     TRACE_FUNCTION("String.formatted")
     return ReturnTuple(VAR_SELF);
+  }
+
+  ReturnTuple Call_hashed(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("String.hashed")
+    PrimInt hash = 1000000009ULL;
+    for (char c : value_) {
+      hash = hash * 1000000009ULL + (unsigned long long) c * 1000000007ULL;
+    }
+    return ReturnTuple(Box_Int(hash));
   }
 
   ReturnTuple Call_readAt(const ParamsArgs& params_args) const final {
diff --git a/example/hello/hello-demo.0rx b/example/hello/hello-demo.0rx
--- a/example/hello/hello-demo.0rx
+++ b/example/hello/hello-demo.0rx
@@ -5,24 +5,20 @@
 define HelloDemo {
   run () {
     scoped {
-      TextReader reader <- TextReader.fromBlockReader(SimpleInput.stdin())
+      TextReader reader <- TextReader.fromBlockReader(BasicInput.stdin())
     } cleanup {
-      \ LazyStream<Formatted>.new()
-          .append("Goodbye.\n")
-          .writeTo(SimpleOutput.stderr())
+      \ BasicOutput.stderr().writeNow("Goodbye.\n")
     } in while (!reader.pastEnd()) {
-      \ LazyStream<Formatted>.new()
-          .append("What is your name? ")
-          .writeTo(SimpleOutput.stderr())
+      \ BasicOutput.stderr().writeNow("What is your name? ")
       String name <- reader.readNextLine()
       if (name.size() == 0) {
         break
       }
-      \ LazyStream<Formatted>.new()
-          .append("Hello \"")
-          .append(name)
-          .append("\", if that's your real name.\n")
-          .writeTo(SimpleOutput.stderr())
+      \ BasicOutput.stderr()
+          .write("Hello \"")
+          .write(name)
+          .write("\", if that's your real name.\n")
+          .flush()
     }
   }
 }
diff --git a/example/parser/parser.0rx b/example/parser/parser.0rx
--- a/example/parser/parser.0rx
+++ b/example/parser/parser.0rx
@@ -94,7 +94,7 @@
 
   @category new (String) -> (ParseState<any>)
   new (data) {
-    return ParseState<any>{ data, 0, 1, 1, ErrorOr:value(Void.void()), empty }
+    return ParseState<any>{ data, 0, 1, 1, ErrorOr:value(Void.default()), empty }
   }
 
   @value getError () -> (Formatted)
@@ -109,18 +109,18 @@
   @value sanityCheck () -> ()
   sanityCheck () {
     if (hasBrokenInput()) {
-      \ LazyStream<Formatted>.new()
-          .append("Error at ")
-          .append(getPosition())
-          .append(": ")
-          .append(getError())
-          .writeTo(SimpleOutput.error())
+      \ BasicOutput.error()
+          .write("Error at ")
+          .write(getPosition())
+          .write(": ")
+          .write(getError())
+          .flush()
     }
     if (atEof()) {
-      \ LazyStream<Formatted>.new()
-          .append("Reached end of input at ")
-          .append(getPosition())
-          .writeTo(SimpleOutput.error())
+      \ BasicOutput.error()
+          .write("Reached end of input at ")
+          .write(getPosition())
+          .flush()
     }
   }
 }
diff --git a/example/primes/prime-tracker.0rx b/example/primes/prime-tracker.0rx
--- a/example/primes/prime-tracker.0rx
+++ b/example/primes/prime-tracker.0rx
@@ -3,7 +3,7 @@
   @value Int nextPossible
 
   create () {
-    return PrimeTracker{ Vector:create<Int>(), 2 }
+    return PrimeTracker{ Vector<Int>.new(), 2 }
   }
 
   checkNextPossible (flag) {
@@ -31,10 +31,10 @@
     nextPossible <- nextPossible+1
 
     if (isPrime) {
-      \ LazyStream<Formatted>.new()
-          .append("\r")
-          .append(toCheck)
-          .writeTo(SimpleOutput.stderr())
+      \ BasicOutput.stderr()
+          .write("\r")
+          .write(toCheck)
+          .flush()
       \ primes.push(toCheck)
     }
     return self
diff --git a/example/primes/primes-demo.0rx b/example/primes/primes-demo.0rx
--- a/example/primes/primes-demo.0rx
+++ b/example/primes/primes-demo.0rx
@@ -11,13 +11,12 @@
     // Interactive input loop.
 
     scoped {
-      TextReader reader <- TextReader.fromBlockReader(SimpleInput.stdin())
+      TextReader reader <- TextReader.fromBlockReader(BasicInput.stdin())
     } in while (!reader.pastEnd()) {
       $Hidden[tracker,thread]$
 
-      \ LazyStream<Formatted>.new()
-          .append("Press [Enter] to toggle start/stop computation thread. Type \"exit\" to exit.\n")
-          .writeTo(SimpleOutput.stderr())
+      \ BasicOutput.stderr()
+          .writeNow("Press [Enter] to toggle start/stop computation thread. Type \"exit\" to exit.\n")
       String input <- reader.readNextLine()
       if (reader.pastEnd()) {
         break
@@ -26,37 +25,29 @@
       if (input == "") {
         if (flag.getEnabled()) {
           \ flag.stop()
-          \ LazyStream<Formatted>.new()
-              .append("Stopped.\n")
-              .writeTo(SimpleOutput.stderr())
+          \ BasicOutput.stderr().writeNow("Stopped.\n")
         } else {
           \ flag.start()
-          \ LazyStream<Formatted>.new()
-              .append("Started.\n")
-              .writeTo(SimpleOutput.stderr())
+          \ BasicOutput.stderr().writeNow("Started.\n")
         }
       } elif (input == "exit") {
         break
       } else {
-        \ LazyStream<Formatted>.new()
-            .append("Please try again!\n")
-            .writeTo(SimpleOutput.stderr())
+          \ BasicOutput.stderr().writeNow("Please try again!\n")
       }
     }
 
     // Wait for the thread to exit, then print the results.
 
     \ flag.cancel()
-    \ LazyStream<Formatted>.new()
-        .append("Exiting.\n")
-        .writeTo(SimpleOutput.stderr())
+    \ BasicOutput.stderr().writeNow("Exiting.\n")
     \ thread.join()
 
     traverse (tracker.getResults() -> Int prime) {
-      \ LazyStream<Formatted>.new()
-          .append(prime)
-          .append("\n")
-          .writeTo(SimpleOutput.stdout())
+      \ BasicOutput.stdout()
+          .write(prime)
+          .write("\n")
+          .flush()
     }
   }
 }
diff --git a/example/random/random-demo.0rx b/example/random/random-demo.0rx
--- a/example/random/random-demo.0rx
+++ b/example/random/random-demo.0rx
@@ -42,9 +42,7 @@
         .setWeight('y',1974)
         .setWeight('z',74)
 
-    // We could use weights.getTotal() as the upper limit here, but setting it
-    // to 1.0 allows us to account for changes to weights.
-    Generator<Float> letter <- RandomUniform.new(0.0,1.0)
+    Generator<Char>  letter <- weights `RandomCategorical:sampleWith` RandomUniform.probability()
     Generator<Float> length <- RandomGaussian.new(5.0,3.0)
     Generator<Float> timing <- RandomExponential.new(4.0)
 
@@ -58,15 +56,14 @@
 
       // Populate the letters using their relative frequencies.
       traverse (Counter.zeroIndexed(buffer.size()) -> Int pos) {
-        Int offset <- (weights.getTotal().asFloat()*letter.generate()).asInt()
-        \ buffer.writeAt(pos,weights.locate(offset))
+        \ buffer.writeAt(pos,letter.generate())
       }
 
       // Print the word.
-      \ LazyStream<Formatted>.new()
-          .append(String.fromCharBuffer(buffer))
-          .append("\n")
-          .writeTo(SimpleOutput.stdout())
+      \ BasicOutput.stdout()
+          .write(String.fromCharBuffer(buffer))
+          .write("\n")
+          .flush()
 
       // Wait a random amount of time.
       \ Realtime.sleepSeconds(timing.generate())
diff --git a/lib/container/.zeolite-module b/lib/container/.zeolite-module
--- a/lib/container/.zeolite-module
+++ b/lib/container/.zeolite-module
@@ -1,5 +1,23 @@
 root: "../.."
 path: "lib/container"
+expression_map: [
+  expression_macro {
+    name: HASH_TABLE_MIN_SIZE
+    expression: 1
+  }
+  expression_macro {
+    // Overfilling bins makes container iteration more efficient because we
+    // don't have as many empty bins to skip over.
+    name: HASH_TABLE_EXPAND_RATIO
+    expression: 2
+  }
+  expression_macro {
+    // Assumption: Container iteration is less likely when the caller is
+    // removing a lot of elements, so we allow more underfilled bins.
+    name: HASH_TABLE_CONTRACT_RATIO
+    expression: 3
+  }
+]
 extra_paths: [
   "lib/container/src"
   "lib/container/test"
diff --git a/lib/container/auto-tree.0rp b/lib/container/auto-tree.0rp
--- a/lib/container/auto-tree.0rp
+++ b/lib/container/auto-tree.0rp
@@ -52,19 +52,19 @@
   // Get the value associated with the key.
   @value get (#k) -> (optional #v)
 
-  // Set the value associated with the key.
+  // Set the value associated with the key if it is not present.
   //
   // 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) -> ()
+  @value weakSet (#k,#v) -> (#v)
 
-  // Remove the node associated with the key.
+  // Swap the value associated with the key if it is not present.
   //
   // 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) -> ()
+  @value swap (#k,optional #v) -> (optional #v)
 }
 
 // An interface for reading the state of a BST node.
diff --git a/lib/container/hashed-map.0rp b/lib/container/hashed-map.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/hashed-map.0rp
@@ -0,0 +1,40 @@
+/* -----------------------------------------------------------------------------
+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 hash table for key-value storage.
+//
+// Params:
+// - #k: The key type.
+// - #v: The value type.
+concrete HashedMap<#k,#v> {
+  defines Default
+  refines Container
+  refines DefaultOrder<KeyValue<#k,#v>>
+  refines Duplicate
+  refines KeyOrder<#k>
+  refines KVExchange<#k,#v>
+  refines KVWriter<#k,#v>
+  refines KVReader<#k,#v>
+  refines ValueOrder<#v>
+  #k immutable
+  #k defines Equals<#k>
+  #k requires Hashed
+
+  // Create an empty table.
+  @type new () -> (#self)
+}
diff --git a/lib/container/hashed-set.0rp b/lib/container/hashed-set.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/hashed-set.0rp
@@ -0,0 +1,37 @@
+/* -----------------------------------------------------------------------------
+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 hash-based set.
+//
+// Params:
+// - #k: The element type.
+concrete HashedSet<#k> {
+  defines Default
+  refines Append<#k>
+  refines Container
+  refines DefaultOrder<#k>
+  refines Duplicate
+  refines SetReader<#k>
+  refines SetWriter<#k>
+  #k immutable
+  #k defines Equals<#k>
+  #k requires Hashed
+
+  // Create a new set.
+  @type new () -> (#self)
+}
diff --git a/lib/container/interfaces.0rp b/lib/container/interfaces.0rp
--- a/lib/container/interfaces.0rp
+++ b/lib/container/interfaces.0rp
@@ -51,12 +51,46 @@
   get (#k) -> (optional #v)
 }
 
+// Reading keys from key-value storage.
+@value interface KeyOrder<|#k> {
+  // Returns an iterator for the container's keys.
+  keyOrder () -> (optional Order<#k>)
+}
+
+// Reading values from key-value storage.
+@value interface ValueOrder<|#v> {
+  // Returns an iterator for the container's values.
+  valueOrder () -> (optional Order<#v>)
+}
+
+// Exchanging values in key-value storage.
+//
+// Params:
+// - #k: The key type.
+// - #v: The value type.
+@value interface KVExchange<#k|#v|> {
+  // Replaces the value if it does not exist and returns the final value.
+  weakSet (#k,#v) -> (#v)
+
+  // Swap the value associated with the key.
+  //
+  // Args:
+  // - #k: Key to look up.
+  // - optional #v: Replacement value. If empty, remove the entry.
+  //
+  // Returns:
+  // - optional #v: Previous value, or empty if none existed.
+  swap (#k,optional #v) -> (optional #v)
+}
+
 // Writing to a set of values.
 //
 // Params:
 // - #k: The type stored in the set.
 @value interface SetWriter<#k|> {
-  add (#k)    -> (#self)
+  // Adds the value to the set.
+  add (#k) -> (#self)
+  // Removes the value.
   remove (#k) -> (#self)
 }
 
@@ -65,6 +99,7 @@
 // Params:
 // - #k: The type stored in the set.
 @value interface SetReader<#k|> {
+  // Returns true if the set contains the value.
   member (#k) -> (Bool)
 }
 
diff --git a/lib/container/search-tree.0rp b/lib/container/search-tree.0rp
deleted file mode 100644
--- a/lib/container/search-tree.0rp
+++ /dev/null
@@ -1,66 +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]
-
-// A binary search tree for key-value storage.
-//
-// Params:
-// - #k: The key type.
-// - #v: The value type.
-concrete SearchTree<#k,#v> {
-  refines Container
-  refines DefaultOrder<KeyValue<#k,#v>>
-  refines Duplicate
-  refines KVWriter<#k,#v>
-  refines KVReader<#k,#v>
-  #k immutable
-  #k defines LessThan<#k>
-
-  // Create an empty tree.
-  @type new () -> (#self)
-
-  // Traverse in the default order.
-  //
-  // Notes:
-  // - This is from DefaultOrder, but is made explicit for documentation.
-  // - Traversal of the entire tree 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<KeyValue<#k,#v>>)
-
-  // Traverse the tree in the reverse order of defaultOrder().
-  //
-  // Notes:
-  // - Traversal of the entire tree 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<KeyValue<#k,#v>>)
-
-  // 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<KeyValue<#k,#v>>)
-
-  // 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<KeyValue<#k,#v>>)
-}
diff --git a/lib/container/sorted-map.0rp b/lib/container/sorted-map.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/sorted-map.0rp
@@ -0,0 +1,70 @@
+/* -----------------------------------------------------------------------------
+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]
+
+// A binary search map for key-value storage.
+//
+// Params:
+// - #k: The key type.
+// - #v: The value type.
+concrete SortedMap<#k,#v> {
+  defines Default
+  refines Container
+  refines DefaultOrder<KeyValue<#k,#v>>
+  refines Duplicate
+  refines KeyOrder<#k>
+  refines KVExchange<#k,#v>
+  refines KVWriter<#k,#v>
+  refines KVReader<#k,#v>
+  refines ValueOrder<#v>
+  #k immutable
+  #k defines LessThan<#k>
+
+  // Create an empty map.
+  @type new () -> (#self)
+
+  // Traverse in the default order.
+  //
+  // Notes:
+  // - This is from DefaultOrder, but is made explicit for documentation.
+  // - Traversal of the entire map 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<KeyValue<#k,#v>>)
+
+  // Traverse the map in the reverse order of defaultOrder().
+  //
+  // Notes:
+  // - Traversal of the entire map 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<KeyValue<#k,#v>>)
+
+  // Start forward traversal (same as defaultOrder()) from the specified key.
+  //
+  // Notes:
+  // - If the key does not exist in the SortedMap, the position right after
+  //   where it would be (in the forward direction) is returned.
+  @value getForward (#k) -> (optional Order<KeyValue<#k,#v>>)
+
+  // Start reverse traversal (same as reverseOrder()) from the specified key.
+  //
+  // Notes:
+  // - If the key does not exist in the SortedMap, the position right after
+  //   where it would be (in the reverse direction) is returned.
+  @value getReverse (#k) -> (optional Order<KeyValue<#k,#v>>)
+}
diff --git a/lib/container/sorted-set.0rp b/lib/container/sorted-set.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/sorted-set.0rp
@@ -0,0 +1,67 @@
+/* -----------------------------------------------------------------------------
+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 map-based set.
+//
+// Params:
+// - #k: The element type.
+concrete SortedSet<#k> {
+  defines Default
+  refines Append<#k>
+  refines Container
+  refines DefaultOrder<#k>
+  refines Duplicate
+  refines SetReader<#k>
+  refines SetWriter<#k>
+  #k immutable
+  #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 SortedMap, 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 SortedMap, 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/sorting.0rp b/lib/container/sorting.0rp
--- a/lib/container/sorting.0rp
+++ b/lib/container/sorting.0rp
@@ -48,6 +48,10 @@
     #xx defines LessThan<#x>
   ([ReadAt<#x>&WriteAt<#x>]) -> ()
 
+  // The same as sortWith, but with LessThan2 instead of LessThan.
+  @category sortWith2<#x>
+  ([ReadAt<#x>&WriteAt<#x>],LessThan2<#x>) -> ()
+
   // In-place order reversal of a random-access container.
   @category reverse<#x> ([ReadAt<#x>&WriteAt<#x>]) -> ()
 
@@ -94,6 +98,11 @@
     #xx defines LessThan<#x>
     #n requires ListNode<#n,#x>
   (optional #n) -> (optional #n)
+
+  // The same as sortListWith, but with LessThan2 instead of LessThan.
+  @category sortListWith2<#n,#x>
+    #n requires ListNode<#n,#x>
+  (optional #n,LessThan2<#x>) -> (optional #n)
 
   // Reverses the list in place and returns the new head.
   //
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
@@ -24,6 +24,7 @@
 #include "Category_Container.hpp"
 #include "Category_Default.hpp"
 #include "Category_DefaultOrder.hpp"
+#include "Category_Duplicate.hpp"
 #include "Category_Formatted.hpp"
 #include "Category_Order.hpp"
 #include "Category_ReadAt.hpp"
@@ -41,31 +42,13 @@
 BoxedValue CreateValue_Vector(S<const Type_Vector> parent, VectorType values);
 
 struct ExtCategory_Vector : public Category_Vector {
-  ReturnTuple Call_copyFrom(const ParamsArgs& params_args) final {
-    TRACE_FUNCTION("Vector:copyFrom")
-    const S<const TypeInstance> Param_y = params_args.GetParam(0);
-    const BoxedValue& Var_arg1 = (params_args.GetArg(0));
-    VectorType values;
-    const PrimInt size = TypeValue::Call(Var_arg1, Function_Container_size, PassParamsArgs()).At(0).AsInt();
-    for (int i = 0; i < size; ++i) {
-      values.push_back(TypeValue::Call(Var_arg1, Function_ReadAt_readAt, PassParamsArgs(Box_Int(i))).At(0));
-    }
-    return ReturnTuple(CreateValue_Vector(CreateType_Vector(Params<1>::Type(Param_y)), std::move(values)));
-  }
-
-  ReturnTuple Call_create(const ParamsArgs& params_args) final {
-    TRACE_FUNCTION("Vector:create")
-    const S<const TypeInstance> Param_y = params_args.GetParam(0);
-    return ReturnTuple(CreateValue_Vector(CreateType_Vector(Params<1>::Type(Param_y)), VectorType()));
-  }
-
   ReturnTuple Call_createSize(const ParamsArgs& params_args) final {
     TRACE_FUNCTION("Vector:createSize")
     const S<const TypeInstance> Param_y = params_args.GetParam(0);
     const PrimInt Var_arg1 = (params_args.GetArg(0)).AsInt();
-    VectorType values;
+    VectorType values(Var_arg1);
     for (int i = 0; i < Var_arg1; ++i) {
-      values.push_back(TypeInstance::Call(Param_y, Function_Default_default, PassParamsArgs()).At(0));
+      values[i] = TypeInstance::Call(Param_y, Function_Default_default, PassParamsArgs()).At(0);
     }
     return ReturnTuple(CreateValue_Vector(CreateType_Vector(Params<1>::Type(Param_y)), std::move(values)));
   }
@@ -73,6 +56,16 @@
 
 struct ExtType_Vector : public Type_Vector {
   inline ExtType_Vector(Category_Vector& p, Params<1>::Type params) : Type_Vector(p, params) {}
+
+  ReturnTuple Call_new(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Vector.new")
+    return ReturnTuple(CreateValue_Vector(PARAM_SELF, VectorType()));
+  }
+
+  ReturnTuple Call_default(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("Vector.default")
+    return Call_new(PassParamsArgs());
+  }
 };
 
 class VectorOrder : public TypeValue {
diff --git a/lib/container/src/auto-tree.0rx b/lib/container/src/auto-tree.0rx
--- a/lib/container/src/auto-tree.0rx
+++ b/lib/container/src/auto-tree.0rx
@@ -44,58 +44,71 @@
     return find(root,k)
   }
 
-  set (k,v) {
-    root, Bool added <- insert(root,k,v)
-    if (added) {
+  weakSet (k,v) {
+    root, optional #v v2 <- insertWeak(root,k,v)
+    if (`present` v2) {
+      return `require` v2
+    } else {
       treeSize <- treeSize+1
+      return v
     }
   }
 
-  remove (k) {
-    root, Bool removed <- delete(root,k)
-    if (removed) {
+  swap (k,v) (old) {
+    root, old <- exchange(root,k,v)
+    if (!present(v) && present(old)) {
       treeSize <- treeSize-1
+    } elif (present(v) && !present(old)) {
+      treeSize <- treeSize+1
     }
   }
 
-
-  @type insert (optional #n,#k,#v) -> (optional #n,Bool)
-  insert (node,k,v) (newRoot,added) {
+  @type exchange (optional #n,#k,optional #v) -> (optional #n,optional #v)
+  exchange (node,k,v) (newRoot,old) {
     if (!present(node)) {
-      return #n.newNode(k,v), true
+      if (present(v)) {
+        return #n.newNode(k,require(v)), empty
+      } else {
+        return empty, empty
+      }
     }
     #n node2 <- require(node)
     if (k `#k.lessThan` node2.getKey()) {
-      newRoot, added <- insert(node2.getLower(),k,v)
+      newRoot, old <- exchange(node2.getLower(),k,v)
       \ node2.setLower(newRoot)
       newRoot <- rebalance(node2)
     } elif (node2.getKey() `#k.lessThan` k) {
-      newRoot, added <- insert(node2.getHigher(),k,v)
+      newRoot, old <- exchange(node2.getHigher(),k,v)
       \ node2.setHigher(newRoot)
       newRoot <- rebalance(node2)
-    } else {
-      \ node2.setValue(v)
+    } elif (present(v)) {
+      old <- node2.getValue()
+      newRoot <- node2
+      \ node2.setValue(require(v))
       \ node2.updateNode()
-      return node2, false
+    } else {
+      old <- node2.getValue()
+      newRoot <- rebalance(removeNode(node2))
     }
   }
 
-  @type delete (optional #n,#k) -> (optional #n,Bool)
-  delete (node,k) (newRoot,removed) {
+
+  @type insertWeak (optional #n,#k,#v) -> (optional #n,optional #v)
+  insertWeak (node,k,v) (newRoot,value) {
     if (!present(node)) {
-      return empty, false
+      return #n.newNode(k,v), empty
     }
     #n node2 <- require(node)
     if (k `#k.lessThan` node2.getKey()) {
-      newRoot, removed <- delete(node2.getLower(),k)
+      newRoot, value <- insertWeak(node2.getLower(),k,v)
       \ node2.setLower(newRoot)
       newRoot <- rebalance(node2)
     } elif (node2.getKey() `#k.lessThan` k) {
-      newRoot, removed <- delete(node2.getHigher(),k)
+      newRoot, value <- insertWeak(node2.getHigher(),k,v)
       \ node2.setHigher(newRoot)
       newRoot <- rebalance(node2)
     } else {
-      return rebalance(removeNode(node2)), true
+      return node2, node2.getValue()
     }
   }
 
@@ -234,31 +247,6 @@
   }
 }
 
-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]$
 
@@ -304,7 +292,7 @@
   }
 
   get () {
-    return TreeKeyValue:create(node.getKey(),node.getValue())
+    return SimpleKeyValue:new(node.getKey(),node.getValue())
   }
 }
 
@@ -353,6 +341,6 @@
   }
 
   get () {
-    return TreeKeyValue:create(node.getKey(),node.getValue())
+    return SimpleKeyValue:new(node.getKey(),node.getValue())
   }
 }
diff --git a/lib/container/src/hashed-map.0rx b/lib/container/src/hashed-map.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/src/hashed-map.0rx
@@ -0,0 +1,295 @@
+/* -----------------------------------------------------------------------------
+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 HashedMap {
+  @value Int itemCount
+  @value [DefaultOrder<HashRoot<#k,#v>>&ReadAt<HashRoot<#k,#v>>] table
+
+  new () {
+    return #self{ 0, Vector:createSize<HashRoot<#k,#v>>($ExprLookup[HASH_TABLE_MIN_SIZE]$) }
+  }
+
+  default () {
+    return new()
+  }
+
+  duplicate () {
+    return #self{ itemCount, table.defaultOrder() `OrderH:duplicateTo` Vector<HashRoot<#k,#v>>.new() }
+  }
+
+  size () {
+    return itemCount
+  }
+
+  get (k) {
+    return table.readAt(getBin(k.hashed())).find(k)
+  }
+
+  set (k,v) {
+    \ swap(k,v)
+    return self
+  }
+
+  remove (k) {
+    \ swap(k,empty)
+    return self
+  }
+
+  weakSet (k,v) {
+    optional #v value <- table.readAt(getBin(k.hashed())).weakReplace(k,v)
+    if (`present` value) {
+      return `require` value
+    } else {
+      itemCount <- itemCount+1
+      \ maybeResize()
+      return v
+    }
+  }
+
+  swap (k,v) (old) {
+    old <- table.readAt(getBin(k.hashed())).exchange(k,v)
+    if (!present(v) && present(old)) {
+      itemCount <- itemCount-1
+      \ maybeResize()
+    } elif (present(v) && !present(old)) {
+      itemCount <- itemCount+1
+      \ maybeResize()
+    }
+  }
+
+  defaultOrder () {
+    return HashedMapOrder<#k,#v>.new(itemCount,table)
+  }
+
+  keyOrder () {
+    return MapKeyOrder:new(defaultOrder())
+  }
+
+  valueOrder () {
+    return MapValueOrder:new(defaultOrder())
+  }
+
+  @value getBin (Int) -> (Int)
+  getBin (hash) {
+    if (hash < 0) {
+      return -hash % table.size()
+    } else {
+      return hash % table.size()
+    }
+  }
+
+  @value maybeResize () -> ()
+  maybeResize () {
+    optional [DefaultOrder<HashRoot<#k,#v>>&ReadAt<HashRoot<#k,#v>>] newTable <- empty
+    if (itemCount >= $ExprLookup[HASH_TABLE_EXPAND_RATIO]$*table.size()) {
+      newTable <- Vector:createSize<HashRoot<#k,#v>>(2*table.size())
+    } elif ($ExprLookup[HASH_TABLE_CONTRACT_RATIO]$*itemCount < table.size() && table.size() > $ExprLookup[HASH_TABLE_MIN_SIZE]$) {
+      newTable <- Vector:createSize<HashRoot<#k,#v>>(table.size()/2)
+    }
+    if (`present` newTable) {
+      [DefaultOrder<HashRoot<#k,#v>>&ReadAt<HashRoot<#k,#v>>] oldTable <- table
+      table <- `require` newTable
+      $Hidden[newTable]$
+      traverse (oldTable.defaultOrder() -> HashRoot<#k,#v> root) {
+        traverse (root.defaultOrder() -> HashNode<#k,#v> node) {
+          \ table.readAt(getBin(node.getKey().hashed())).exchange(node.getKey(),node.getValue())
+        }
+      }
+    }
+  }
+}
+
+concrete HashNode<#k,#v> {
+  refines Duplicate
+  refines Order<#self>
+
+  @type new (#k,#v,optional HashNode<#k,#v>) -> (#self)
+  @value getKey () -> (#k)
+  @value getValue () -> (#v)
+  @value setValue (#v) -> ()
+  @value setNext (optional HashNode<#k,#v>) -> (optional #self)
+}
+
+define HashNode {
+  $ReadOnly[key]$
+
+  @value #k key
+  @value #v value
+  @value optional HashNode<#k,#v> next
+
+  new (key,value,next) {
+    return #self{ key, value, next }
+  }
+
+  duplicate () {
+    optional HashNode<#k,#v> next2 <- empty
+    if (`present` next) {
+      next2 <- require(next).duplicate()
+    }
+    return #self{ key, value, next2 }
+  }
+
+  getKey () { return key }
+  getValue () { return value }
+  setValue (value2) { value <- value2 }
+  get () { return self }
+  next () { return next }
+  setNext (next2) {
+    cleanup {
+      next <- next2
+    } in return next
+  }
+}
+
+concrete HashRoot<#k,#v> {
+  defines Default
+  refines DefaultOrder<HashNode<#k,#v>>
+  refines Duplicate
+  #k defines Equals<#k>
+
+  @value exchange (#k,optional #v) -> (optional #v)
+  @value weakReplace (#k,#v) -> (optional #v)
+  @value find (#k) -> (optional #v)
+  // Override to allow returning HashNode directly.
+  @value defaultOrder () -> (optional HashNode<#k,#v>)
+}
+
+define HashRoot {
+  @value optional HashNode<#k,#v> root
+
+  default () {
+    return #self{ empty }
+  }
+
+  defaultOrder () {
+    return root
+  }
+
+  duplicate () {
+    if (`present` root) {
+      return #self{ require(root).duplicate() }
+    } else {
+      return #self{ empty }
+    }
+  }
+
+  exchange (k,v) (old) {
+    old <- empty
+    scoped {
+      optional HashNode<#k,#v> prev <- empty
+    } in traverse (root -> HashNode<#k,#v> node) {
+      if (node.getKey() `#k.equals` k) {
+        old <- node.getValue()
+        if (`present` v) {
+          \ node.setValue(`require` v)
+        } else {
+          optional HashNode<#k,#v> next <- node.setNext(empty)
+          if (`present` prev) {
+            \ require(prev).setNext(next)
+          } else {
+            root <- next
+          }
+        }
+        return _
+      }
+    } update {
+      prev <- node
+    }
+    if (`present` v) {
+      root <- HashNode<#k,#v>.new(k,`require` v,root)
+    }
+  }
+
+  weakReplace (k,v) {
+    traverse (root -> HashNode<#k,#v> node) {
+      if (node.getKey() `#k.equals` k) {
+        return node.getValue()
+      }
+    }
+    root <- HashNode<#k,#v>.new(k,v,root)
+    return empty
+  }
+
+  find (k) (v) {
+    v <- empty
+    traverse (root -> HashNode<#k,#v> node) {
+      if (node.getKey() `#k.equals` k) {
+        return node.getValue()
+      }
+    }
+  }
+}
+
+concrete HashedMapOrder<#k,#v> {
+  refines Order<KeyValue<#k,#v>>
+
+  @type new (Int,DefaultOrder<HashRoot<#k,#v>>) -> (optional #self)
+}
+
+define HashedMapOrder {
+  @value Int count
+  @value optional Order<HashRoot<#k,#v>> root
+  @value HashNode<#k,#v> node
+
+  new (count,table) (next) {
+    next <- empty
+    if (count > 0) {
+      scoped {
+        optional Order<HashRoot<#k,#v>> root, optional HashNode<#k,#v> node <- seekNext(table.defaultOrder())
+      } in if (`present` node) {
+        next <- #self{ count, root, `require` node }
+      }
+    }
+  }
+
+  get () {
+    return SimpleKeyValue:new(node.getKey(),node.getValue())
+  }
+
+  next () (next) {
+    next <- empty
+    if ((count <- count-1) > 0) {
+      scoped {
+        optional HashNode<#k,#v> newNode <- node.next()
+      } in if (`present` newNode) {
+        node <- `require` newNode
+        return self
+      }
+      scoped {
+        root, optional HashNode<#k,#v> newNode <- seekNext(root)
+      } in if (`present` newNode) {
+        node <- `require` newNode
+        return self
+      }
+    }
+  }
+
+  @type seekNext (optional Order<HashRoot<#k,#v>>) -> (optional Order<HashRoot<#k,#v>>,optional HashNode<#k,#v>)
+  seekNext (start) (root,node) {
+    root <- start
+    node <- empty
+    $Hidden[start]$
+    while (`present` root) {
+      node <- require(root).get().defaultOrder()
+      root <- require(root).next()
+      if (`present` node) {
+        return _
+      }
+    }
+  }
+}
diff --git a/lib/container/src/hashed-set.0rx b/lib/container/src/hashed-set.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/src/hashed-set.0rx
@@ -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]
+
+define HashedSet {
+  @value HashedMap<#k,any> map
+
+  default () {
+    return new()
+  }
+
+  new () {
+    return #self{ HashedMap<#k,any>.new() }
+  }
+
+  size () {
+    return map.size()
+  }
+
+  duplicate () {
+    return #self{ map.duplicate() }
+  }
+
+  add (k) {
+    \ map.set(k,Void.default())
+    return self
+  }
+
+  append (k) {
+    return add(k)
+  }
+
+  remove (k) {
+    \ map.remove(k)
+    return self
+  }
+
+  member (k) {
+    return present(map.get(k))
+  }
+
+  defaultOrder () {
+    return MapKeyOrder:new(map.defaultOrder())
+  }
+}
diff --git a/lib/container/src/search-tree.0rx b/lib/container/src/search-tree.0rx
deleted file mode 100644
--- a/lib/container/src/search-tree.0rx
+++ /dev/null
@@ -1,195 +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]
-
-define SearchTree {
-  @value AutoBinaryTree<SearchTreeNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>> tree
-
-  new () {
-    return #self{ AutoBinaryTree<SearchTreeNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>>.new() }
-  }
-
-  size () {
-    return tree.size()
-  }
-
-  duplicate () {
-    return #self{ tree.duplicate() }
-  }
-
-  set (k,v) {
-    \ tree.set(k,v)
-    return self
-  }
-
-  remove (k) {
-    \ tree.remove(k)
-    return self
-  }
-
-  get (k) {
-    return tree.get(k)
-  }
-
-  defaultOrder () {
-    return ForwardTreeOrder:create(tree.getRoot())
-  }
-
-  reverseOrder () {
-    return ReverseTreeOrder:create(tree.getRoot())
-  }
-
-  getForward (k) {
-    return ForwardTreeOrder:seek(k,tree.getRoot())
-  }
-
-  getReverse (k) {
-    return ReverseTreeOrder:seek(k,tree.getRoot())
-  }
-}
-
-define ValidatedTree {
-  @value AutoBinaryTree<SearchTreeNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>> tree
-
-  new () { $NoTrace$
-    return #self{ AutoBinaryTree<SearchTreeNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>>.new() }
-  }
-
-  size () { $NoTrace$
-    return tree.size()
-  }
-
-  set (k,v) { $NoTrace$
-    \ tree.set(k,v)
-    \ validate(tree.getRoot())
-    return self
-  }
-
-  remove (k) { $NoTrace$
-    \ tree.remove(k)
-    \ validate(tree.getRoot())
-    return self
-  }
-
-  get (k) { $NoTrace$
-    return tree.get(k)
-  }
-
-  @type validate (optional BinaryTreeNode<#k,#v>) -> ()
-  validate (node) { $NoTrace$
-    if (present(node)) {
-      \ validateOrder(require(node))
-      \ validateBalance(require(node))
-    }
-  }
-
-  @type validateOrder (BinaryTreeNode<#k,#v>) -> ()
-  validateOrder (node) { $NoTrace$
-    if (present(node.getLower())) {
-      if (!(require(node.getLower()).getKey() `#k.lessThan` node.getKey())) {
-        fail("bad lower order")
-      }
-      \ validateOrder(require(node.getLower()))
-    }
-    if (present(node.getHigher())) {
-      if (!(node.getKey() `#k.lessThan` require(node.getHigher()).getKey())) {
-        fail("bad higher order")
-      }
-      \ validateOrder(require(node.getHigher()))
-    }
-  }
-
-  @type validateBalance (BinaryTreeNode<#k,#v>) -> ()
-  validateBalance (node) { $NoTrace$
-    scoped {
-      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(node.getLower())) {
-      \ validateBalance(require(node.getLower()))
-    }
-    if (present(node.getHigher())) {
-      \ validateBalance(require(node.getHigher()))
-    }
-  }
-}
-
-concrete SearchTreeNode<#k,#v> {
-  defines KVFactory<#k,#v>
-  refines BalancedTreeNode<SearchTreeNode<#k,#v>,#k,#v>
-  refines Duplicate
-}
-
-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 }
-  }
-
-  duplicate () {
-    optional #self lower2 <- empty
-    if (present(lower)) {
-      lower2 <- require(lower).duplicate()
-    }
-    optional #self higher2 <- empty
-    if (present(higher)) {
-      higher2 <- require(higher).duplicate()
-    }
-    return #self{ height, key, value, lower2, higher2 }
-  }
-
-  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
-      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
-    }
-  }
-}
diff --git a/lib/container/src/sorted-map.0rx b/lib/container/src/sorted-map.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/src/sorted-map.0rx
@@ -0,0 +1,215 @@
+/* -----------------------------------------------------------------------------
+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 SortedMap {
+  @value AutoBinaryTree<SortedMapNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>> map
+
+  default () {
+    return new()
+  }
+
+  new () {
+    return #self{ AutoBinaryTree<SortedMapNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>>.new() }
+  }
+
+  size () {
+    return map.size()
+  }
+
+  duplicate () {
+    return #self{ map.duplicate() }
+  }
+
+  get (k) {
+    return map.get(k)
+  }
+
+  set (k,v) {
+    \ swap(k,v)
+    return self
+  }
+
+  remove (k) {
+    \ swap(k,empty)
+    return self
+  }
+
+  weakSet (k,v) {
+    return map.weakSet(k,v)
+  }
+
+  swap (k,v) {
+    return map.swap(k,v)
+  }
+
+  defaultOrder () {
+    return ForwardTreeOrder:create(map.getRoot())
+  }
+
+  reverseOrder () {
+    return ReverseTreeOrder:create(map.getRoot())
+  }
+
+  getForward (k) {
+    return ForwardTreeOrder:seek(k,map.getRoot())
+  }
+
+  getReverse (k) {
+    return ReverseTreeOrder:seek(k,map.getRoot())
+  }
+
+  keyOrder () {
+    return MapKeyOrder:new(defaultOrder())
+  }
+
+  valueOrder () {
+    return MapValueOrder:new(defaultOrder())
+  }
+}
+
+define ValidatedTree {
+  @value AutoBinaryTree<SortedMapNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>> map
+
+  new () { $NoTrace$
+    return #self{ AutoBinaryTree<SortedMapNode<#k,#v>,#k,#v,BinaryTreeNode<#k,#v>>.new() }
+  }
+
+  size () { $NoTrace$
+    return map.size()
+  }
+
+  set (k,v) { $NoTrace$
+    \ map.swap(k,v)
+    \ validate(map.getRoot())
+    return self
+  }
+
+  remove (k) { $NoTrace$
+    \ map.swap(k,empty)
+    \ validate(map.getRoot())
+    return self
+  }
+
+  get (k) { $NoTrace$
+    return map.get(k)
+  }
+
+  @type validate (optional BinaryTreeNode<#k,#v>) -> ()
+  validate (node) { $NoTrace$
+    if (present(node)) {
+      \ validateOrder(require(node))
+      \ validateBalance(require(node))
+    }
+  }
+
+  @type validateOrder (BinaryTreeNode<#k,#v>) -> ()
+  validateOrder (node) { $NoTrace$
+    if (present(node.getLower())) {
+      if (!(require(node.getLower()).getKey() `#k.lessThan` node.getKey())) {
+        fail("bad lower order")
+      }
+      \ validateOrder(require(node.getLower()))
+    }
+    if (present(node.getHigher())) {
+      if (!(node.getKey() `#k.lessThan` require(node.getHigher()).getKey())) {
+        fail("bad higher order")
+      }
+      \ validateOrder(require(node.getHigher()))
+    }
+  }
+
+  @type validateBalance (BinaryTreeNode<#k,#v>) -> ()
+  validateBalance (node) { $NoTrace$
+    scoped {
+      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(node.getLower())) {
+      \ validateBalance(require(node.getLower()))
+    }
+    if (present(node.getHigher())) {
+      \ validateBalance(require(node.getHigher()))
+    }
+  }
+}
+
+concrete SortedMapNode<#k,#v> {
+  defines KVFactory<#k,#v>
+  refines BalancedTreeNode<SortedMapNode<#k,#v>,#k,#v>
+  refines Duplicate
+}
+
+define SortedMapNode {
+  $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 }
+  }
+
+  duplicate () {
+    optional #self lower2 <- empty
+    if (present(lower)) {
+      lower2 <- require(lower).duplicate()
+    }
+    optional #self higher2 <- empty
+    if (present(higher)) {
+      higher2 <- require(higher).duplicate()
+    }
+    return #self{ height, key, value, lower2, higher2 }
+  }
+
+  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
+      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
+    }
+  }
+}
diff --git a/lib/container/src/sorted-set.0rx b/lib/container/src/sorted-set.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/src/sorted-set.0rx
@@ -0,0 +1,71 @@
+/* -----------------------------------------------------------------------------
+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 SortedSet {
+  @value SortedMap<#k,any> map
+
+  default () {
+    return new()
+  }
+
+  new () {
+    return #self{ SortedMap<#k,any>.new() }
+  }
+
+  size () {
+    return map.size()
+  }
+
+  duplicate () {
+    return #self{ map.duplicate() }
+  }
+
+  add (k) {
+    \ map.set(k,Void.default())
+    return self
+  }
+
+  append (k) {
+    return add(k)
+  }
+
+  remove (k) {
+    \ map.remove(k)
+    return self
+  }
+
+  member (k) {
+    return present(map.get(k))
+  }
+
+  defaultOrder () {
+    return MapKeyOrder:new(map.defaultOrder())
+  }
+
+  reverseOrder () {
+    return MapKeyOrder:new(map.reverseOrder())
+  }
+
+  getForward (k) {
+    return MapKeyOrder:new(map.getForward(k))
+  }
+
+  getReverse (k) {
+    return MapKeyOrder:new(map.getReverse(k))
+  }
+}
diff --git a/lib/container/src/sorting.0rx b/lib/container/src/sorting.0rx
--- a/lib/container/src/sorting.0rx
+++ b/lib/container/src/sorting.0rx
@@ -22,9 +22,13 @@
   }
 
   sortWith (seq) {
-    \ HeapSort<#x,#xx>.inPlace(seq)
+    \ seq `sortWith2` AsLessThan2<#x,#xx>.new()
   }
 
+  sortWith2 (seq,compare) {
+    \ HeapSort<#x>.inPlace(seq,compare)
+  }
+
   reverse (seq) {
     scoped {
       Int i <- seq.size()/2-1
@@ -44,9 +48,13 @@
   }
 
   sortListWith (head) {
-    return MergeSort<#n,#x,#xx>.sort(head)
+    return head `sortListWith2` AsLessThan2<#x,#xx>.new()
   }
 
+  sortListWith2 (head,compare) {
+    return MergeSort<#n,#x>.sort(head,compare)
+  }
+
   reverseList (head) (head2) {
     if (!present(head)) {
       return empty
@@ -71,19 +79,18 @@
 
 // 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>]) -> ()
+concrete HeapSort<#x> {
+  @type inPlace ([ReadAt<#x>&WriteAt<#x>],LessThan2<#x>) -> ()
 }
 
 define HeapSort {
-  $ReadOnly[seq]$
+  $ReadOnly[seq,compare]$
 
   @value [ReadAt<#x>&WriteAt<#x>] seq
+  @value LessThan2<#x> compare
 
-  inPlace (seq) {
-    \ #self{ seq }.execute()
+  inPlace (seq,compare) {
+    \ #self{ seq, compare }.execute()
   }
 
   @value execute () -> ()
@@ -121,10 +128,10 @@
       Int left  <- 2*last+1
       Int right <- 2*last+2
       $ReadOnly[left,right]$
-      if (seq.readAt(indexLargest) `#xx.lessThan` seq.readAt(left)) {
+      if (seq.readAt(indexLargest) `compare.lessThan2` seq.readAt(left)) {
         indexLargest <- left
       }
-      if (right < size && seq.readAt(indexLargest) `#xx.lessThan` seq.readAt(right)) {
+      if (right < size && seq.readAt(indexLargest) `compare.lessThan2` seq.readAt(right)) {
         indexLargest <- right
       }
       if (indexLargest == last) {
@@ -148,15 +155,21 @@
 
 // Putting the params at the top level allows helpers to be called without
 // needing to pass the params every time.
-concrete MergeSort<#n,#x,#xx> {
-  #xx defines LessThan<#x>
+concrete MergeSort<#n,#x> {
   #n requires ListNode<#n,#x>
 
-  @type sort (optional #n) -> (optional #n)
+  @type sort (optional #n,LessThan2<#x>) -> (optional #n)
 }
 
 define MergeSort {
-  sort (head) (head2) {
+  @value LessThan2<#x> compare
+
+  sort (head,compare) {
+    return #self{ compare }.iterated(head)
+  }
+
+  @value iterated (optional #n) -> (optional #n)
+  iterated (head) (head2) {
     head2 <- head
     $Hidden[head]$
 
@@ -190,7 +203,7 @@
     }
   }
 
-  @type splitAt (optional #n,Int) -> (optional #n)
+  @value splitAt (optional #n,Int) -> (optional #n)
   splitAt (head,n) {
     optional #n head2 <- head
     $Hidden[head]$
@@ -210,7 +223,7 @@
     return empty
   }
 
-  @type merge (optional #n,optional #n) -> (optional #n,optional #n)
+  @value merge (optional #n,optional #n) -> (optional #n,optional #n)
   merge (left,right) (head,tail) {
     head <- empty
     tail <- empty
@@ -221,7 +234,7 @@
 
     while (present(left2) && present(right2)) {
       #n append <- defer
-      if (require(left2).get() `#xx.lessThan` require(right2).get()) {
+      if (require(left2).get() `compare.lessThan2` require(right2).get()) {
         left2 <- (append <- require(left2)).next()
       } else {
         right2 <- (append <- require(right2)).next()
diff --git a/lib/container/src/tree-set.0rx b/lib/container/src/tree-set.0rx
deleted file mode 100644
--- a/lib/container/src/tree-set.0rx
+++ /dev/null
@@ -1,100 +0,0 @@
-/* -----------------------------------------------------------------------------
-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()
-  }
-
-  duplicate () {
-    return #self{ tree.duplicate() }
-  }
-
-  add (k) {
-    \ tree.set(k,Void.void())
-    return self
-  }
-
-  append (k) {
-    return add(k)
-  }
-
-  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/src/type-map.0rx b/lib/container/src/type-map.0rx
--- a/lib/container/src/type-map.0rx
+++ b/lib/container/src/type-map.0rx
@@ -17,29 +17,33 @@
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
 define TypeMap {
-  @value SearchTree<TypeKey<any>,GenericValue<any>> tree
+  @value SortedMap<TypeKey<any>,GenericValue<any>> map
 
+  default () {
+    return new()
+  }
+
   new () {
-    return TypeMap{ SearchTree<TypeKey<any>,GenericValue<any>>.new() }
+    return TypeMap{ SortedMap<TypeKey<any>,GenericValue<any>>.new() }
   }
 
   duplicate () {
-    return #self{ tree.duplicate() }
+    return #self{ map.duplicate() }
   }
 
   set (k,v) {
-    \ tree.set(k,GenericValue:create(v))
+    \ map.set(k,GenericValue:create(v))
     return self
   }
 
   remove (k) {
-    \ tree.remove(k)
+    \ map.remove(k)
     return self
   }
 
   get (k) {
     scoped {
-      optional GenericValue<any> value <- tree.get(k)
+      optional GenericValue<any> value <- map.get(k)
     } in if (present(value)) {
       return require(value).check<#x>()
     } else {
@@ -48,7 +52,7 @@
   }
 
   getAll (output) {
-    traverse (tree.defaultOrder() -> KeyValue<any,GenericValue<any>> pair) {
+    traverse (map.defaultOrder() -> KeyValue<any,GenericValue<any>> pair) {
       scoped {
         optional #x value <- pair.getValue().check<#x>()
       } in if (present(value)) {
@@ -81,6 +85,10 @@
         .append(index)
         .append("}")
         .build()
+  }
+
+  hashed () {
+    return index.hashed()
   }
 
   lessThan (l,r) {
diff --git a/lib/container/src/validated-tree.0rp b/lib/container/src/validated-tree.0rp
--- a/lib/container/src/validated-tree.0rp
+++ b/lib/container/src/validated-tree.0rp
@@ -21,7 +21,7 @@
 // A validated binary search tree for key-value storage.
 //
 // Notes:
-// - This is the same as SearchTree, but it validates the tree structure every
+// - This is the same as SortedMap, 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|> {
diff --git a/lib/container/src/wrappers.0rp b/lib/container/src/wrappers.0rp
new file mode 100644
--- /dev/null
+++ b/lib/container/src/wrappers.0rp
@@ -0,0 +1,31 @@
+/* -----------------------------------------------------------------------------
+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$
+
+concrete SimpleKeyValue<|#k,#v> {
+  @category new<#k,#v> (#k,#v) -> (KeyValue<#k,#v>)
+}
+
+concrete MapKeyOrder<#k> {
+  @category new<#k> (optional Order<KeyValue<#k,any>>) -> (optional Order<#k>)
+}
+
+concrete MapValueOrder<#v> {
+  @category new<#v> (optional Order<KeyValue<any,#v>>) -> (optional Order<#v>)
+}
diff --git a/lib/container/src/wrappers.0rx b/lib/container/src/wrappers.0rx
new file mode 100644
--- /dev/null
+++ b/lib/container/src/wrappers.0rx
@@ -0,0 +1,96 @@
+/* -----------------------------------------------------------------------------
+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 SimpleKeyValue {
+  $ReadOnly[key,value]$
+
+  refines KeyValue<#k,#v>
+
+  @value #k key
+  @value #v value
+
+  new (key,value) {
+    return SimpleKeyValue<#k,#v>{ key, value }
+  }
+
+  getKey () {
+    return key
+  }
+
+  getValue () {
+    return value
+  }
+}
+
+define MapKeyOrder {
+  refines Order<#k>
+
+  @value Order<KeyValue<#k,any>> order
+
+  new (order) {
+    if (!present(order)) {
+      return empty
+    } else {
+      return MapKeyOrder<#k>{ 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()
+  }
+}
+
+define MapValueOrder {
+  refines Order<#v>
+
+  @value Order<KeyValue<any,#v>> order
+
+  new (order) {
+    if (!present(order)) {
+      return empty
+    } else {
+      return MapValueOrder<#v>{ require(order) }
+    }
+  }
+
+  next () {
+    scoped {
+      optional Order<KeyValue<any,#v>> order2 <- order.next()
+    } in if (present(order2)) {
+      order <- require(order2)
+      return self
+    } else {
+      return empty
+    }
+  }
+
+  get () {
+    return order.get().getValue()
+  }
+}
diff --git a/lib/container/test/hashed-map.0rt b/lib/container/test/hashed-map.0rt
new file mode 100644
--- /dev/null
+++ b/lib/container/test/hashed-map.0rt
@@ -0,0 +1,239 @@
+/* -----------------------------------------------------------------------------
+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 "HashedMap tests" {
+  success
+}
+
+unittest integrationTest {
+  HashedMap<Int,Int> map <- HashedMap<Int,Int>.default()
+  Int count <- 33
+  $ReadOnly[count]$
+
+  // Insert values.
+  traverse (Counter.zeroIndexed(count) -> Int i) {
+    Int new <- ((i + 13) * 3547) % count
+    \ map.set(new,i)
+    \ Testing.checkEquals(map.size(),i+1)
+  }
+
+  // Check and remove values.
+  traverse (Counter.zeroIndexed(count) -> Int i) {
+    Int new <- ((i + 13) * 3547) % count
+    scoped {
+      optional Int value <- map.get(new)
+    } in if (!present(value)) {
+      \ BasicOutput.error()
+          .write("Not found ")
+          .write(new)
+          .write(" but should have been ")
+          .write(i)
+          .flush()
+    } elif (require(value) != i) {
+      \ BasicOutput.error()
+          .write("Element ")
+          .write(new)
+          .write(" should have been ")
+          .write(i)
+          .write(" but was ")
+          .write(require(value))
+          .flush()
+    }
+    \ map.remove(new)
+    \ Testing.checkEquals(map.size(),count-i-1)
+  }
+}
+
+unittest removeNotPresent {
+  HashedMap<Int,Int> map <- HashedMap<Int,Int>.new().set(1,1).set(2,2).set(4,4)
+  \ map.remove(3)
+  \ Testing.checkEquals(map.size(),3)
+}
+
+unittest defaultOrder {
+  HashedMap<Int,Int> map <- HashedMap<Int,Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the map in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ ((i*hash)%max) `map.set` i
+  }
+
+  Vector<Bool> visited <- Vector:createSize<Bool>(max)
+
+  // Validate traversal coverage.
+  Int index <- 0
+  traverse (map.defaultOrder() -> KeyValue<Int,Int> entry) {
+    \ Testing.checkFalse(visited.readAt(entry.getKey()))
+    \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
+    \ visited.writeAt(entry.getKey(),true)
+    index <- index+1
+  }
+
+  \ Testing.checkEquals(index,max)
+}
+
+unittest defaultOrderEmpty {
+  traverse (HashedMap<Int,Int>.new().defaultOrder() -> _) {
+    fail("not empty")
+  }
+}
+
+unittest keyOrder {
+  HashedMap<Int,Int> map <- HashedMap<Int,Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the map in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ ((i*hash)%max) `map.set` i
+  }
+
+  Vector<Bool> visited <- Vector:createSize<Bool>(max)
+
+  // Validate traversal coverage.
+  Int index <- 0
+  traverse (map.keyOrder() -> Int key) {
+    \ Testing.checkFalse(visited.readAt(key))
+    \ visited.writeAt(key,true)
+    index <- index+1
+  }
+
+  \ Testing.checkEquals(index,max)
+}
+
+unittest valueOrder {
+  HashedMap<Int,Int> map <- HashedMap<Int,Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the map in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ ((i*hash)%max) `map.set` i
+  }
+
+  Vector<Bool> visited <- Vector:createSize<Bool>(max)
+
+  // Validate traversal coverage.
+  Int index <- 0
+  traverse (map.valueOrder() -> Int value) {
+    \ Testing.checkFalse(visited.readAt(value))
+    \ visited.writeAt(value,true)
+    index <- index+1
+  }
+
+  \ Testing.checkEquals(index,max)
+}
+
+unittest duplicate {
+  HashedMap<Int,Int> map <- HashedMap<Int,Int>.default()
+  Int max <- 31
+  $ReadOnly[max]$
+
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ map.set(i,i)
+  }
+
+  HashedMap<Int,Int> copy <- map.duplicate()
+  \ Testing.checkEquals(copy.size(),map.size())
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    $Hidden[map]$
+    \ Testing.checkOptional(copy.get(i),i)
+  }
+
+  \ copy.set(2,5)
+  \ copy.remove(7)
+  $Hidden[copy]$
+
+  \ Testing.checkEquals(map.size(),max)
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ Testing.checkOptional(map.get(i),i)
+  }
+}
+
+unittest duplicateEmpty {
+  HashedMap<Int,Int> map <- HashedMap<Int,Int>.new()
+  HashedMap<Int,Int> copy <- map.duplicate()
+  \ map.set(1,1)
+  \ Testing.checkEquals(copy.size(),0)
+}
+
+unittest weakSetExists {
+  HashedMap<Int,Value> map <- HashedMap<Int,Value>.default()
+      .set(1,Value.new(1))
+      .set(2,Value.new(2))
+      .set(3,Value.new(3))
+
+  Value value0 <- Value.new(5)
+  Value value1 <- map.weakSet(1,value0)
+  \ Testing.checkEquals(map.size(),3)
+
+  \ Testing.checkEquals(value0.get(),5)
+  \ Testing.checkEquals(value1.get(),1)
+
+  \ value0.set(10)
+  \ Testing.checkEquals(value1.get(),1)
+  \ Testing.checkOptional(map.get(1),Value.new(1))
+}
+
+unittest weakSetMissing {
+  HashedMap<Int,Value> map <- HashedMap<Int,Value>.default()
+      .set(1,Value.new(1))
+      .set(2,Value.new(2))
+      .set(3,Value.new(3))
+
+  Value value0 <- Value.new(5)
+  Value value1 <- map.weakSet(7,value0)
+  \ Testing.checkEquals(map.size(),4)
+
+  \ Testing.checkEquals(value0.get(),5)
+  \ Testing.checkEquals(value1.get(),5)
+
+  \ value0.set(10)
+  \ Testing.checkEquals(value1.get(),10)
+  \ Testing.checkOptional(map.get(7),Value.new(10))
+}
+
+concrete Value {
+  defines Equals<Value>
+  refines Formatted
+
+  @type new (Int) -> (Value)
+  @value get () -> (Int)
+  @value set (Int) -> ()
+}
+
+define Value {
+  @value Int value
+
+  new (value) { return Value{ value } }
+  get () { return value }
+  set (value2) { value <- value2 }
+
+  formatted () {
+    return value.formatted()
+  }
+
+  equals (x,y) {
+    return x.get() == y.get()
+  }
+}
diff --git a/lib/container/test/hashed-set.0rt b/lib/container/test/hashed-set.0rt
new file mode 100644
--- /dev/null
+++ b/lib/container/test/hashed-set.0rt
@@ -0,0 +1,105 @@
+/* -----------------------------------------------------------------------------
+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 "HashedSet tests" {
+  success
+}
+
+unittest addAndRemove {
+  HashedSet<Int> set <- HashedSet<Int>.default()
+  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 append {
+  HashedSet<Int> set <- HashedSet<Int>.new()
+  \ set.append(3)
+  \ Testing.checkEquals(set.size(),1)
+  \ Testing.checkTrue(set.member(3))
+}
+
+unittest removeNotPresent {
+  HashedSet<Int> set <- HashedSet<Int>.new().add(1).add(2).add(4)
+  \ set.remove(3)
+  \ Testing.checkEquals(set.size(),3)
+}
+
+unittest defaultOrder {
+  HashedSet<Int> set <- HashedSet<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)
+  }
+
+  Vector<Bool> visited <- Vector:createSize<Bool>(max)
+
+  // Validate traversal coverage.
+  Int index <- 0
+  traverse (set.defaultOrder() -> Int entry) {
+    \ Testing.checkFalse(visited.readAt(entry))
+    \ visited.writeAt(entry,true)
+    index <- index+1
+  }
+
+  \ Testing.checkEquals(index,max)
+}
+
+unittest defaultOrderEmpty {
+  traverse (HashedSet<Int>.new().defaultOrder() -> _) {
+    fail("not empty")
+  }
+}
+
+unittest duplicate {
+  HashedSet<Int> set <- HashedSet<Int>.new().add(1).add(2)
+
+  HashedSet<Int> copy <- set.duplicate()
+  \ Testing.checkEquals(copy.size(),2)
+  \ Testing.checkTrue(copy.member(1))
+  \ Testing.checkTrue(copy.member(1))
+
+  \ copy.remove(2)
+  \ Testing.checkEquals(copy.size(),1)
+  \ Testing.checkTrue(copy.member(1))
+  \ Testing.checkTrue(set.member(2))
+}
diff --git a/lib/container/test/helpers.0rt b/lib/container/test/helpers.0rt
--- a/lib/container/test/helpers.0rt
+++ b/lib/container/test/helpers.0rt
@@ -59,14 +59,14 @@
 }
 
 unittest integrationTest {
-  SearchTree<Int,String> tree1 <- SearchTree<Int,String>.new()
+  SortedMap<Int,String> map1 <- SortedMap<Int,String>.new()
       .set(1,"d").set(2,"c").set(3,"b").set(4,"a")
-  SearchTree<Int,String> tree2 <- SearchTree<Int,String>.new()
+  SortedMap<Int,String> map2 <- SortedMap<Int,String>.new()
       .set(1,"a").set(2,"b").set(3,"c").set(4,"d")
 
-  \ Testing.checkTrue(tree1.defaultOrder() `OrderH:equalsWith<?,KVEquals<Int,AlwaysEqual>>`    tree2.defaultOrder())
-  \ Testing.checkFalse(tree1.defaultOrder() `OrderH:equalsWith<?,KVEquals<Int,String>>`         tree2.defaultOrder())
-  \ Testing.checkFalse(tree1.defaultOrder() `OrderH:equalsWith<?,KVEquals<AlwaysEqual,String>>` tree2.defaultOrder())
+  \ Testing.checkTrue(map1.defaultOrder() `OrderH:equalsWith<?,KVEquals<Int,AlwaysEqual>>`    map2.defaultOrder())
+  \ Testing.checkFalse(map1.defaultOrder() `OrderH:equalsWith<?,KVEquals<Int,String>>`         map2.defaultOrder())
+  \ Testing.checkFalse(map1.defaultOrder() `OrderH:equalsWith<?,KVEquals<AlwaysEqual,String>>` map2.defaultOrder())
 }
 
 concrete KVEquals<#k,#v> {
diff --git a/lib/container/test/list.0rt b/lib/container/test/list.0rt
--- a/lib/container/test/list.0rt
+++ b/lib/container/test/list.0rt
@@ -31,7 +31,7 @@
       .append(4)
       .build()
 
-  DefaultOrder<Int> expected <- Vector:create<Int>()
+  DefaultOrder<Int> expected <- Vector<Int>.new()
       .append(3)
       .append(2)
       .append(5)
@@ -58,7 +58,7 @@
       .append(4)
       .build()
 
-  DefaultOrder<Int> expected <- Vector:create<Int>()
+  DefaultOrder<Int> expected <- Vector<Int>.new()
       .append(4)
       .append(6)
       .append(1)
@@ -228,7 +228,7 @@
       .append(4)
       .build()
 
-  DefaultOrder<Int> expected <- Vector:create<Int>()
+  DefaultOrder<Int> expected <- Vector<Int>.new()
       .append(3)
       .append(2)
       .append(5)
@@ -266,7 +266,7 @@
       .append(4)
       .build()
 
-  DefaultOrder<Int> expected <- Vector:create<Int>()
+  DefaultOrder<Int> expected <- Vector<Int>.new()
       .append(3)
       .append(2)
       .append(5)
@@ -292,7 +292,7 @@
       .append(4)
       .build()
 
-  DefaultOrder<Int> expected <- Vector:create<Int>()
+  DefaultOrder<Int> expected <- Vector<Int>.new()
       .append(3)
       .append(2)
       .append(5)
diff --git a/lib/container/test/search-tree.0rt b/lib/container/test/search-tree.0rt
deleted file mode 100644
--- a/lib/container/test/search-tree.0rt
+++ /dev/null
@@ -1,213 +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]
-
-testcase "SearchTree tests" {
-  success
-}
-
-unittest integrationTest {
-  ValidatedTree<Int,Int> tree <- ValidatedTree<Int,Int>.new()
-  Int count <- 33
-  $ReadOnly[count]$
-
-  // Insert values.
-  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.
-  traverse (Counter.zeroIndexed(count) -> Int i) {
-    Int new <- ((i + 13) * 3547) % count
-    scoped {
-      optional Int value <- tree.get(new)
-    } in if (!present(value)) {
-      \ LazyStream<Formatted>.new()
-          .append("Not found ")
-          .append(new)
-          .append(" but should have been ")
-          .append(i)
-          .writeTo(SimpleOutput.error())
-    } elif (require(value) != i) {
-      \ LazyStream<Formatted>.new()
-          .append("Element ")
-          .append(new)
-          .append(" should have been ")
-          .append(i)
-          .append(" but was ")
-          .append(require(value))
-          .writeTo(SimpleOutput.error())
-    }
-    \ tree.remove(new)
-    \ Testing.checkEquals(tree.size(),count-i-1)
-  }
-}
-
-unittest defaultOrder {
-  SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()
-  Int hash <- 13
-  Int max  <- 20
-  $ReadOnly[hash,max]$
-
-  // Populate the tree in a pseudo-random order.
-  traverse (Counter.zeroIndexed(max) -> Int i) {
-    \ ((i*hash)%max) `tree.set` i
-  }
-
-  // Validate the traversal order.
-  Int index <- 0
-  traverse (tree.defaultOrder() -> KeyValue<Int,Int> entry) {
-    \ Testing.checkEquals(entry.getKey(),index)
-    \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
-    index <- index+1
-  }
-
-  \ Testing.checkEquals(index,max)
-}
-
-unittest reverseOrder {
-  SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()
-  Int hash <- 13
-  Int max  <- 20
-  $ReadOnly[hash,max]$
-
-  // Populate the tree in a pseudo-random order.
-  traverse (Counter.zeroIndexed(max) -> Int i) {
-    \ ((i*hash)%max) `tree.set` i
-  }
-
-  // Validate the traversal order.
-  Int index <- max
-  traverse (tree.reverseOrder() -> KeyValue<Int,Int> entry) {
-    index <- index-1
-    \ Testing.checkEquals(entry.getKey(),index)
-    \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
-  }
-
-  \ Testing.checkEquals(index,0)
-}
-
-unittest getForward {
-  SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()
-  Int hash <- 13
-  Int max  <- 20
-  $ReadOnly[hash,max]$
-
-  // Populate the tree in a pseudo-random order.
-  traverse (Counter.zeroIndexed(max) -> Int i) {
-    \ ((i*hash)%max) `tree.set` i
-  }
-
-  // Validate the traversal order from every starting point.
-  traverse (Counter.zeroIndexed(max) -> Int i) {
-    Int index <- i
-    traverse (tree.getForward(index) -> KeyValue<Int,Int> entry) {
-      \ Testing.checkEquals(entry.getKey(),index)
-      \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
-      index <- index+1
-    }
-    \ Testing.checkEquals(index,max)
-  }
-}
-
-unittest getForwardNotFound {
-  SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()
-  \ tree.set(1,1).set(3,3).set(5,5)
-
-  scoped {
-    optional Order<KeyValue<Int,Int>> start <- tree.getForward(4)
-  } in if (!present(start)) {
-    fail("Failed")
-  } else {
-    \ Testing.checkEquals(require(start).get().getKey(),5)
-  }
-
-  scoped {
-    optional Order<KeyValue<Int,Int>> start <- tree.getForward(6)
-  } in if (present(start)) {
-    fail("Failed")
-  }
-}
-
-unittest getReverse {
-  SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()
-  Int hash <- 13
-  Int max  <- 20
-  $ReadOnly[hash,max]$
-
-  // Populate the tree in a pseudo-random order.
-  traverse (Counter.zeroIndexed(max) -> Int i) {
-    \ ((i*hash)%max) `tree.set` i
-  }
-
-  // Validate the traversal order from every starting point.
-  traverse (Counter.zeroIndexed(max) -> Int i) {
-    Int index <- i
-    traverse (tree.getReverse(index) -> KeyValue<Int,Int> entry) {
-      \ Testing.checkEquals(entry.getKey(),index)
-      \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
-      index <- index-1
-    }
-    \ Testing.checkEquals(index,-1)
-  }
-}
-
-unittest getReverseNotFound {
-  SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()
-  \ tree.set(1,1).set(3,3).set(5,5)
-
-  scoped {
-    optional Order<KeyValue<Int,Int>> start <- tree.getReverse(2)
-  } in if (!present(start)) {
-    fail("Failed")
-  } else {
-    \ Testing.checkEquals(require(start).get().getKey(),1)
-  }
-
-  scoped {
-    optional Order<KeyValue<Int,Int>> start <- tree.getReverse(0)
-  } in if (present(start)) {
-    fail("Failed")
-  }
-}
-
-unittest duplicate {
-  SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new().set(1,1).set(2,2).set(3,3)
-
-  SearchTree<Int,Int> copy <- tree.duplicate()
-  \ Testing.checkEquals(copy.size(),3)
-  \ Testing.checkOptional(copy.get(1),1)
-  \ Testing.checkOptional(copy.get(2),2)
-  \ Testing.checkOptional(copy.get(3),3)
-
-  \ copy.remove(2)
-  $Hidden[copy]$
-
-  \ Testing.checkEquals(tree.size(),3)
-  \ Testing.checkOptional(tree.get(1),1)
-  \ Testing.checkOptional(tree.get(2),2)
-  \ Testing.checkOptional(tree.get(3),3)
-}
-
-unittest duplicateEmpty {
-  SearchTree<Int,Int> tree <- SearchTree<Int,Int>.new()
-
-  SearchTree<Int,Int> copy <- tree.duplicate()
-  \ Testing.checkEquals(copy.size(),0)
-}
diff --git a/lib/container/test/sorted-map.0rt b/lib/container/test/sorted-map.0rt
new file mode 100644
--- /dev/null
+++ b/lib/container/test/sorted-map.0rt
@@ -0,0 +1,336 @@
+/* -----------------------------------------------------------------------------
+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]
+
+testcase "SortedMap tests" {
+  success
+}
+
+unittest integrationTest {
+  ValidatedTree<Int,Int> map <- ValidatedTree<Int,Int>.new()
+  Int count <- 33
+  $ReadOnly[count]$
+
+  // Insert values.
+  traverse (Counter.zeroIndexed(count) -> Int i) {
+    Int new <- ((i + 13) * 3547) % count
+    \ map.set(new,i)
+    \ Testing.checkEquals(map.size(),i+1)
+  }
+
+  // Check and remove values.
+  traverse (Counter.zeroIndexed(count) -> Int i) {
+    Int new <- ((i + 13) * 3547) % count
+    scoped {
+      optional Int value <- map.get(new)
+    } in if (!present(value)) {
+      \ BasicOutput.error()
+          .write("Not found ")
+          .write(new)
+          .write(" but should have been ")
+          .write(i)
+          .flush()
+    } elif (require(value) != i) {
+      \ BasicOutput.error()
+          .write("Element ")
+          .write(new)
+          .write(" should have been ")
+          .write(i)
+          .write(" but was ")
+          .write(require(value))
+          .flush()
+    }
+    \ map.remove(new)
+    \ Testing.checkEquals(map.size(),count-i-1)
+  }
+}
+
+unittest removeNotPresent {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new().set(1,1).set(2,2).set(4,4)
+  \ map.remove(3)
+  \ Testing.checkEquals(map.size(),3)
+}
+
+unittest defaultOrder {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the map in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ ((i*hash)%max) `map.set` i
+  }
+
+  // Validate the traversal order.
+  Int index <- 0
+  traverse (map.defaultOrder() -> KeyValue<Int,Int> entry) {
+    \ Testing.checkEquals(entry.getKey(),index)
+    \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
+    index <- index+1
+  }
+
+  \ Testing.checkEquals(index,max)
+}
+
+unittest defaultOrderEmpty {
+  traverse (SortedMap<Int,Int>.new().defaultOrder() -> _) {
+    fail("not empty")
+  }
+}
+
+unittest keyOrder {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the map in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ ((i*hash)%max) `map.set` i
+  }
+
+  // Validate the traversal order.
+  Int index <- 0
+  traverse (map.keyOrder() -> Int key) {
+    \ Testing.checkEquals(key,index)
+    index <- index+1
+  }
+
+  \ Testing.checkEquals(index,max)
+}
+
+unittest valueOrder {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the map in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ ((i*hash)%max) `map.set` i
+  }
+
+  // Validate the traversal order.
+  Int index <- 0
+  traverse (map.valueOrder() -> Int value) {
+    \ Testing.checkEquals((value*hash)%max,index)
+    index <- index+1
+  }
+
+  \ Testing.checkEquals(index,max)
+}
+
+unittest reverseOrder {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the map in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ ((i*hash)%max) `map.set` i
+  }
+
+  // Validate the traversal order.
+  Int index <- max
+  traverse (map.reverseOrder() -> KeyValue<Int,Int> entry) {
+    index <- index-1
+    \ Testing.checkEquals(entry.getKey(),index)
+    \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
+  }
+
+  \ Testing.checkEquals(index,0)
+}
+
+unittest getForward {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the map in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ ((i*hash)%max) `map.set` i
+  }
+
+  // Validate the traversal order from every starting point.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    Int index <- i
+    traverse (map.getForward(index) -> KeyValue<Int,Int> entry) {
+      \ Testing.checkEquals(entry.getKey(),index)
+      \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
+      index <- index+1
+    }
+    \ Testing.checkEquals(index,max)
+  }
+}
+
+unittest getForwardNotFound {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  \ map.set(1,1).set(3,3).set(5,5)
+
+  scoped {
+    optional Order<KeyValue<Int,Int>> start <- map.getForward(4)
+  } in if (!present(start)) {
+    fail("Failed")
+  } else {
+    \ Testing.checkEquals(require(start).get().getKey(),5)
+  }
+
+  scoped {
+    optional Order<KeyValue<Int,Int>> start <- map.getForward(6)
+  } in if (present(start)) {
+    fail("Failed")
+  }
+}
+
+unittest getReverse {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  Int hash <- 13
+  Int max  <- 20
+  $ReadOnly[hash,max]$
+
+  // Populate the map in a pseudo-random order.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ ((i*hash)%max) `map.set` i
+  }
+
+  // Validate the traversal order from every starting point.
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    Int index <- i
+    traverse (map.getReverse(index) -> KeyValue<Int,Int> entry) {
+      \ Testing.checkEquals(entry.getKey(),index)
+      \ Testing.checkEquals((entry.getValue()*hash)%max,entry.getKey())
+      index <- index-1
+    }
+    \ Testing.checkEquals(index,-1)
+  }
+}
+
+unittest getReverseNotFound {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  \ map.set(1,1).set(3,3).set(5,5)
+
+  scoped {
+    optional Order<KeyValue<Int,Int>> start <- map.getReverse(2)
+  } in if (!present(start)) {
+    fail("Failed")
+  } else {
+    \ Testing.checkEquals(require(start).get().getKey(),1)
+  }
+
+  scoped {
+    optional Order<KeyValue<Int,Int>> start <- map.getReverse(0)
+  } in if (present(start)) {
+    fail("Failed")
+  }
+}
+
+unittest duplicate {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.default()
+  Int max <- 31
+  $ReadOnly[max]$
+
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ map.set(i,i)
+  }
+
+  SortedMap<Int,Int> copy <- map.duplicate()
+  \ Testing.checkEquals(copy.size(),map.size())
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    $Hidden[map]$
+    \ Testing.checkOptional(copy.get(i),i)
+  }
+
+  \ copy.set(2,5)
+  \ copy.remove(7)
+  $Hidden[copy]$
+
+  \ Testing.checkEquals(map.size(),max)
+  traverse (Counter.zeroIndexed(max) -> Int i) {
+    \ Testing.checkOptional(map.get(i),i)
+  }
+}
+
+unittest duplicateEmpty {
+  SortedMap<Int,Int> map <- SortedMap<Int,Int>.new()
+  SortedMap<Int,Int> copy <- map.duplicate()
+  \ map.set(1,1)
+  \ Testing.checkEquals(copy.size(),0)
+}
+
+unittest weakSetExists {
+  SortedMap<Int,Value> map <- SortedMap<Int,Value>.default()
+      .set(1,Value.new(1))
+      .set(2,Value.new(2))
+      .set(3,Value.new(3))
+
+  Value value0 <- Value.new(5)
+  Value value1 <- map.weakSet(1,value0)
+  \ Testing.checkEquals(map.size(),3)
+
+  \ Testing.checkEquals(value0.get(),5)
+  \ Testing.checkEquals(value1.get(),1)
+
+  \ value0.set(10)
+  \ Testing.checkEquals(value1.get(),1)
+  \ Testing.checkOptional(map.get(1),Value.new(1))
+}
+
+unittest weakSetMissing {
+  SortedMap<Int,Value> map <- SortedMap<Int,Value>.default()
+      .set(1,Value.new(1))
+      .set(2,Value.new(2))
+      .set(3,Value.new(3))
+
+  Value value0 <- Value.new(5)
+  Value value1 <- map.weakSet(7,value0)
+  \ Testing.checkEquals(map.size(),4)
+
+  \ Testing.checkEquals(value0.get(),5)
+  \ Testing.checkEquals(value1.get(),5)
+
+  \ value0.set(10)
+  \ Testing.checkEquals(value1.get(),10)
+  \ Testing.checkOptional(map.get(7),Value.new(10))
+}
+
+concrete Value {
+  defines Equals<Value>
+  refines Formatted
+
+  @type new (Int) -> (Value)
+  @value get () -> (Int)
+  @value set (Int) -> ()
+}
+
+define Value {
+  @value Int value
+
+  new (value) { return Value{ value } }
+  get () { return value }
+  set (value2) { value <- value2 }
+
+  formatted () {
+    return value.formatted()
+  }
+
+  equals (x,y) {
+    return x.get() == y.get()
+  }
+}
diff --git a/lib/container/test/sorted-set.0rt b/lib/container/test/sorted-set.0rt
new file mode 100644
--- /dev/null
+++ b/lib/container/test/sorted-set.0rt
@@ -0,0 +1,205 @@
+/* -----------------------------------------------------------------------------
+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 "SortedSet tests" {
+  success
+}
+
+unittest addAndRemove {
+  SortedSet<Int> set <- SortedSet<Int>.default()
+  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 append {
+  SortedSet<Int> set <- SortedSet<Int>.new()
+  \ set.append(3)
+  \ Testing.checkEquals(set.size(),1)
+  \ Testing.checkTrue(set.member(3))
+}
+
+unittest removeNotPresent {
+  SortedSet<Int> set <- SortedSet<Int>.new().add(1).add(2).add(4)
+  \ set.remove(3)
+  \ Testing.checkEquals(set.size(),3)
+}
+
+unittest defaultOrder {
+  SortedSet<Int> set <- SortedSet<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 defaultOrderEmpty {
+  traverse (SortedSet<Int>.new().defaultOrder() -> _) {
+    fail("not empty")
+  }
+}
+
+unittest reverseOrder {
+  SortedSet<Int> set <- SortedSet<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 {
+  SortedSet<Int> set <- SortedSet<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 {
+  SortedSet<Int> set <- SortedSet<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 {
+  SortedSet<Int> set <- SortedSet<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 {
+  SortedSet<Int> set <- SortedSet<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")
+  }
+}
+
+unittest duplicate {
+  SortedSet<Int> set <- SortedSet<Int>.new().add(1).add(2)
+
+  SortedSet<Int> copy <- set.duplicate()
+  \ Testing.checkEquals(copy.size(),2)
+  \ Testing.checkTrue(copy.member(1))
+  \ Testing.checkTrue(copy.member(1))
+
+  \ copy.remove(2)
+  \ Testing.checkEquals(copy.size(),1)
+  \ Testing.checkTrue(copy.member(1))
+  \ Testing.checkTrue(set.member(2))
+}
diff --git a/lib/container/test/sorting.0rt b/lib/container/test/sorting.0rt
--- a/lib/container/test/sorting.0rt
+++ b/lib/container/test/sorting.0rt
@@ -107,7 +107,7 @@
 
 unittest sortList {
   ListBuilder<Int,LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
-  Vector<Int> expected <- Vector:create<Int>()
+  Vector<Int> expected <- Vector<Int>.new()
   Int hash <- 269
   Int size <- 137
   $ReadOnly[hash,size]$
@@ -129,7 +129,7 @@
 
 unittest sortListSingle { $DisableCoverage$
   ListBuilder<Int,ForwardNode<Int>> builder <- ForwardNode<Int>.builder()
-  Vector<Int> expected <- Vector:create<Int>()
+  Vector<Int> expected <- Vector<Int>.new()
   Int hash <- 269
   Int size <- 137
   $ReadOnly[hash,size]$
@@ -155,7 +155,7 @@
 
 unittest sortListPow2 { $DisableCoverage$
   ListBuilder<Int,LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
-  Vector<Int> expected <- Vector:create<Int>()
+  Vector<Int> expected <- Vector<Int>.new()
   Int hash <- 269
   Int size <- 256
   $ReadOnly[hash,size]$
@@ -177,7 +177,7 @@
 
 unittest sortListWith { $DisableCoverage$
   ListBuilder<Int,LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
-  Vector<Int> expected <- Vector:create<Int>()
+  Vector<Int> expected <- Vector<Int>.new()
   Int hash <- 269
   Int size <- 137
   $ReadOnly[hash,size]$
@@ -199,7 +199,7 @@
 
 unittest reverseList {
   ListBuilder<Int,LinkedNode<Int>> builder <- LinkedNode<Int>.builder()
-  Vector<Int> expected <- Vector:create<Int>()
+  Vector<Int> expected <- Vector<Int>.new()
   Int size <- 101
   $ReadOnly[size]$
 
@@ -223,7 +223,7 @@
 
 unittest reverseListSingle { $DisableCoverage$
   ListBuilder<Int,ForwardNode<Int>> builder <- ForwardNode<Int>.builder()
-  Vector<Int> expected <- Vector:create<Int>()
+  Vector<Int> expected <- Vector<Int>.new()
   Int size <- 101
   $ReadOnly[size]$
 
@@ -263,7 +263,7 @@
   @value Vector<Int> seq
 
   create () {
-    return TestSequence{ Vector:create<Int>() }
+    return TestSequence{ Vector<Int>.new() }
   }
 
   formatted () {
diff --git a/lib/container/test/tree-set.0rt b/lib/container/test/tree-set.0rt
deleted file mode 100644
--- a/lib/container/test/tree-set.0rt
+++ /dev/null
@@ -1,186 +0,0 @@
-/* -----------------------------------------------------------------------------
-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")
-  }
-}
-
-unittest duplicate {
-  TreeSet<Int> set <- TreeSet<Int>.new().add(1).add(2)
-
-  TreeSet<Int> copy <- set.duplicate()
-  \ Testing.checkEquals(copy.size(),2)
-  \ Testing.checkTrue(copy.member(1))
-  \ Testing.checkTrue(copy.member(1))
-
-  \ copy.remove(2)
-  \ Testing.checkEquals(copy.size(),1)
-  \ Testing.checkTrue(copy.member(1))
-  \ Testing.checkTrue(set.member(2))
-}
diff --git a/lib/container/test/type-map.0rt b/lib/container/test/type-map.0rt
--- a/lib/container/test/type-map.0rt
+++ b/lib/container/test/type-map.0rt
@@ -21,66 +21,66 @@
 }
 
 unittest basicOperations {
-  TypeMap tree <- TypeMap.new()
+  TypeMap map <- TypeMap.default()
 
   TypeKey<Int>    keyInt    <- TypeKey<Int>.new()
   TypeKey<String> keyString <- TypeKey<String>.new()
   TypeKey<Value>  keyValue  <- TypeKey<Value>.new()
 
-  \ Testing.checkOptional(tree.get(keyInt),   empty)
-  \ Testing.checkOptional(tree.get(keyString),empty)
-  \ Testing.checkOptional(tree.get(keyValue), empty)
+  \ Testing.checkOptional(map.get(keyInt),   empty)
+  \ Testing.checkOptional(map.get(keyString),empty)
+  \ Testing.checkOptional(map.get(keyValue), empty)
 
-  \ tree.set(keyInt,1)
-  \ tree.set(keyString,"a")
-  \ tree.set(keyValue,Value.new())
+  \ map.set(keyInt,1)
+  \ map.set(keyString,"a")
+  \ map.set(keyValue,Value.new())
 
-  \ Testing.checkOptional(tree.get(keyInt),   1)
-  \ Testing.checkOptional(tree.get(keyString),"a")
-  \ Testing.checkOptional(tree.get(keyValue), Value.new())
+  \ Testing.checkOptional(map.get(keyInt),   1)
+  \ Testing.checkOptional(map.get(keyString),"a")
+  \ Testing.checkOptional(map.get(keyValue), Value.new())
 
-  \ tree.remove(keyString)
+  \ map.remove(keyString)
 
-  \ Testing.checkOptional(tree.get(keyString),empty)
+  \ Testing.checkOptional(map.get(keyString),empty)
 }
 
 unittest wrongReturnType {
-  TypeMap tree <- TypeMap.new()
+  TypeMap map <- TypeMap.new()
 
   TypeKey<String> keyString <- TypeKey<String>.new()
 
   // Added as Formatted, which means it cannot be retrieved as String later.
-  \ tree.set<Formatted>(keyString,"a")
-  \ Testing.checkOptional(tree.get<String>(keyString),empty)
+  \ map.set<Formatted>(keyString,"a")
+  \ Testing.checkOptional(map.get<String>(keyString),empty)
 
-  \ tree.set<String>(keyString,"a")
-  \ Testing.checkOptional(tree.get<String>(keyString),"a")
+  \ map.set<String>(keyString,"a")
+  \ Testing.checkOptional(map.get<String>(keyString),"a")
 }
 
 unittest duplicate {
   TypeKey<Int>    keyInt    <- TypeKey<Int>.new()
   TypeKey<String> keyString <- TypeKey<String>.new()
 
-  TypeMap tree <- TypeMap.new().set(keyInt,1).set(keyString,"a")
+  TypeMap map <- TypeMap.new().set(keyInt,1).set(keyString,"a")
 
-  TypeMap copy <- tree.duplicate()
+  TypeMap copy <- map.duplicate()
   \ Testing.checkOptional(copy.get(keyInt),1)
   \ Testing.checkOptional(copy.get(keyString),"a")
 
   \ copy.remove(keyString)
   \ Testing.checkOptional(copy.get(keyInt),1)
   \ Testing.checkOptional(copy.get(keyString),empty)
-  \ Testing.checkOptional(tree.get(keyString),"a")
+  \ Testing.checkOptional(map.get(keyString),"a")
 }
 
 unittest getValues {
-  TypeMap tree <- TypeMap.new()
+  TypeMap map <- TypeMap.new()
       .set(TypeKey<String>.new(),"one")
       .set(TypeKey<Int>.new(),2)
       .set(TypeKey<String>.new(),"two")
       .set(TypeKey<Int>.new(),1)
 
-  [Container&SetReader<String>] actual <- tree.getAll(TreeSet<String>.new())
+  [Container&SetReader<String>] actual <- map.getAll(SortedSet<String>.new())
 
   \ Testing.checkEquals(actual.size(),2)
   \ Testing.checkTrue(actual.member("one"))
@@ -94,6 +94,12 @@
   \ Testing.checkEquals<TypeKey<Int>>(key1,key1)
   \ Testing.checkFalse(key1 `TypeKey<any>.equals` key2)
   \ Testing.checkFalse(key1 `TypeKey<any>.equals` key3)
+}
+
+unittest keyHashed {
+  TypeKey<Int> key1 <- TypeKey<Int>.new()
+  TypeKey<Int> key2 <- TypeKey<Int>.new()
+  \ Testing.checkNotEquals(key1.hashed(),key2.hashed())
 }
 
 concrete Value {
diff --git a/lib/container/test/vector.0rt b/lib/container/test/vector.0rt
--- a/lib/container/test/vector.0rt
+++ b/lib/container/test/vector.0rt
@@ -33,22 +33,8 @@
   }
 }
 
-unittest copyFrom {
-  String original <- "abcde"
-  Vector<Char> values <- Vector:copyFrom(original)
-  \ Testing.checkEquals(values.size(),original.size())
-
-  scoped {
-    Int i <- 0
-  } in while (i < original.size()) {
-    \ Testing.checkEquals(values.readAt(i),original.readAt(i))
-  } update {
-    i <- i+1
-  }
-}
-
 unittest append {
-  Vector<Int> values <- Vector:create<Int>()
+  Vector<Int> values <- Vector<Int>.new()
 
   scoped {
     Int i <- 0
@@ -70,7 +56,7 @@
 }
 
 unittest pushAndPop {
-  Vector<Int> values <- Vector:create<Int>()
+  Vector<Int> values <- Vector<Int>.new()
 
   scoped {
     Int i <- 0
@@ -115,7 +101,7 @@
 
 unittest traverseVector {
   Int index <- 0
-  Vector<Int> vector <- Vector:create<Int>()
+  Vector<Int> vector <- Vector<Int>.new()
   \ vector.push(2)
   \ vector.push(6)
   \ vector.push(3)
@@ -132,20 +118,38 @@
 }
 
 unittest duplicate {
-  Vector<Int> vector <- Vector:create<Int>().push(1).push(2)
+  Vector<Int> vector <- Vector<Int>.default().push(1).push(2)
 
   Vector<Int> copy <- vector.duplicate()
   \ Testing.checkEquals(copy.size(),2)
   \ Testing.checkEquals(copy.readAt(0),1)
   \ Testing.checkEquals(copy.readAt(1),2)
 
+  \ copy.writeAt(1,3)
   \ copy.pop()
   \ Testing.checkEquals(copy.size(),1)
   \ Testing.checkEquals(copy.readAt(0),1)
   \ Testing.checkEquals(vector.readAt(1),2)
 }
 
+concrete Value {
+  refines Duplicate
 
+  @type  new (Int) -> (#self)
+  @value set (Int) -> ()
+  @value get ()    -> (Int)
+}
+
+define Value {
+  @value Int value
+
+  new (value)  { return Value{ value } }
+  set (value2) { value <- value2 }
+  get ()       { return value }
+  duplicate () { return new(value) }
+}
+
+
 testcase "distinct default values in pre-sized Vector" {
   success
 }
@@ -211,7 +215,7 @@
 }
 
 unittest test {
-  Vector<Int> values <- Vector:create<Int>()
+  Vector<Int> values <- Vector<Int>.new()
   \ values.readAt(-1)
 }
 
@@ -221,6 +225,6 @@
 }
 
 unittest test {
-  Vector<Int> values <- Vector:create<Int>()
+  Vector<Int> values <- Vector<Int>.new()
   \ values.readAt(1)
 }
diff --git a/lib/container/tree-set.0rp b/lib/container/tree-set.0rp
deleted file mode 100644
--- a/lib/container/tree-set.0rp
+++ /dev/null
@@ -1,62 +0,0 @@
-/* -----------------------------------------------------------------------------
-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 Append<#k>
-  refines Container
-  refines DefaultOrder<#k>
-  refines Duplicate
-  refines SetReader<#k>
-  refines SetWriter<#k>
-  #k immutable
-  #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/type-map.0rp b/lib/container/type-map.0rp
--- a/lib/container/type-map.0rp
+++ b/lib/container/type-map.0rp
@@ -18,6 +18,7 @@
 
 // A map that stores values of arbitrary types.
 concrete TypeMap {
+  defines Default
   refines Duplicate
 
   // Create a new map.
@@ -56,6 +57,7 @@
   immutable
 
   refines Formatted
+  refines Hashed
   defines LessThan<TypeKey<any>>
   defines Equals<TypeKey<any>>
 
diff --git a/lib/container/vector.0rp b/lib/container/vector.0rp
--- a/lib/container/vector.0rp
+++ b/lib/container/vector.0rp
@@ -21,18 +21,17 @@
 // Params:
 // - #x: The value type contained.
 concrete Vector<#x> {
+  defines Default
   refines Append<#x>
   refines DefaultOrder<#x>
+  // Does not duplicate individual elements.
   refines Duplicate
   refines Stack<#x>
   refines ReadAt<#x>
   refines WriteAt<#x>
 
-  // Create a copy from another random-access container.
-  @category copyFrom<#y> (ReadAt<#y>) -> (Vector<#y>)
-
   // Create an empty Vector.
-  @category create<#y> () -> (Vector<#y>)
+  @type new () -> (#self)
 
   // Create a pre-sized vector of default values.
   //
diff --git a/lib/math/categorical-tree.0rp b/lib/math/categorical-tree.0rp
--- a/lib/math/categorical-tree.0rp
+++ b/lib/math/categorical-tree.0rp
@@ -16,10 +16,38 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
+// A read-only categorical distribution.
+//
+// Params:
+// - #c: The type of category (i.e., object) used in the distribution.
+@value interface CategoricalReader<#c> {
+  // Get the sum of all weights in the distribution.
+  getTotal () -> (Int)
+
+  // Get the relative weight of a value.
+  //
+  // Notes:
+  // - If the category isn't present, its weight is 0.
+  getWeight (#c) -> (Int)
+
+  // Return the value 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
+  //   CategoricalReader, each repeated the number of times indicated by its
+  //   respective weight.
+  // - Also see RandomCategorical in random.0rp.
+  locate (Int) -> (#c)
+}
+
 // A categorical distribution represented as a tree.
 //
 // Params:
-// #c: The type of category (i.e., object) used in the distribution.
+// - #c: The type of category (i.e., object) used in the distribution.
 //
 // Notes:
 // - CategoricalTree is intended for use in random sampling of arbitrary objects
@@ -31,17 +59,16 @@
 // - 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>
+  defines Default
+  refines CategoricalReader<#c>
   refines Duplicate
+  refines ReadAt<#c>
   #c immutable
   #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 value.
   //
   // Notes:
@@ -49,12 +76,6 @@
   // - The sum of all weights (see getTotal()) must not exceed Int.maxBound().
   @value setWeight (#c,Int) -> (#self)
 
-  // Get the relative weight of a value.
-  //
-  // Notes:
-  // - If the category isn't in the tree, its weight is 0.
-  @value getWeight (#c) -> (Int)
-
   // Increments the weight of the value by 1.
   @value incrWeight (#c) -> (#self)
 
@@ -64,18 +85,6 @@
   // - Do not call this for values that already have 0 weight. This is primarily
   //   meant for use when locate is used to choose a value to decrement.
   @value decrWeight (#c) -> (#self)
-
-  // Return the value 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)
diff --git a/lib/math/random.0rp b/lib/math/random.0rp
--- a/lib/math/random.0rp
+++ b/lib/math/random.0rp
@@ -50,10 +50,42 @@
   // Creates a new generator with the specified min and max values.
   @type new (Float,Float) -> (#self)
 
+  // Creates a new generator for the range [0.0,1.0).
+  @type probability () -> (#self)
+
   // Resets the seed for RNG.
   @value setSeed (Int) -> (#self)
 }
 
+// Always returns a constant value.
+concrete GenerateConstant {
+  refines Generator<Float>
+
+  // Creates a new generator with the specified constant values.
+  @type new (Float) -> (#self)
+}
+
+// Generates values of the provided type.
+concrete RandomCategorical<|#c> {
+  refines Generator<#c>
+
+  // Creates a generator for the provided distribution.
+  //
+  // Notes:
+  // - The Generator<Float> *must* return values in the range [0,1).
+  // - The distribution is not copied; therefore, changes to the distribution
+  //   will affect the returned generator.
+  @category sampleWith<#c> (CategoricalReader<#c>,Generator<Float>) -> (RandomCategorical<#c>)
+
+  // Returns true if the distribution is empty.
+  //
+  // Note:
+  // - Do not call generate() if this returns true, since there are no values to
+  //   sample from.
+  @value isEmpty () -> (Bool)
+}
+
+// General randomization functions.
 concrete Randomize {
   // Creates a permutation from the CategoricalTree.
   //
@@ -62,5 +94,17 @@
   //
   // Notes:
   // - The Generator must only return values in [0,1).
+  // - If category c has a weight of n, it will occur n times in the output.
   @category permuteFrom<#c> (CategoricalTree<#c>,Generator<Float>,Append<#c>) -> ()
+
+  // Creates a permutation from the CategoricalTree.
+  //
+  // 1. Creates a copy of the CategoricalTree.
+  // 2. Samples from the copy *without* replacement until the tree is empty.
+  //
+  // Notes:
+  // - The Generator must only return values in [0,1).
+  // - If category c has a weight of n, it will occur *once* in the output, but
+  //   it will have a relative chance of n to be chosen ahead of others.
+  @category permuteFromWeight<#c> (CategoricalTree<#c>,Generator<Float>,Append<#c>) -> ()
 }
diff --git a/lib/math/src/Extension_RandomUniform.cpp b/lib/math/src/Extension_RandomUniform.cpp
--- a/lib/math/src/Extension_RandomUniform.cpp
+++ b/lib/math/src/Extension_RandomUniform.cpp
@@ -45,6 +45,11 @@
     }
     return ReturnTuple(CreateValue_RandomUniform(PARAM_SELF, params_args));
   }
+
+  ReturnTuple Call_probability(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("RandomUniform.probability")
+    return Call_new(PassParamsArgs(Box_Float(0.0), Box_Float(1.0)));
+  }
 };
 
 struct ExtValue_RandomUniform : public Value_RandomUniform {
diff --git a/lib/math/src/categorical-tree.0rx b/lib/math/src/categorical-tree.0rx
--- a/lib/math/src/categorical-tree.0rx
+++ b/lib/math/src/categorical-tree.0rx
@@ -19,6 +19,10 @@
 define CategoricalTree {
   @value AutoBinaryTree<CategoricalTreeNode<#c>,#c,Int,CategoricalSearch<#c>> tree
 
+  default () {
+    return new()
+  }
+
   new () {
     return #self{ AutoBinaryTree<CategoricalTreeNode<#c>,#c,Int,CategoricalSearch<#c>>.new() }
   }
@@ -35,9 +39,9 @@
     if (size < 0) {
       fail("size must not be negative")
     } elif (size > 0) {
-      \ tree.set(cat,size)
+      \ tree.swap(cat,size)
     } else {
-      \ tree.remove(cat)
+      \ tree.swap(cat,empty)
     }
     return self
   }
@@ -96,9 +100,9 @@
     if (size < 0) {
       fail("size must not be negative")
     } elif (size > 0) {
-      \ tree.set(cat,size)
+      \ tree.swap(cat,size)
     } else {
-      \ tree.remove(cat)
+      \ tree.swap(cat,empty)
     }
     \ validateTotal(tree.getRoot())
     return self
diff --git a/lib/math/src/random.0rx b/lib/math/src/random.0rx
--- a/lib/math/src/random.0rx
+++ b/lib/math/src/random.0rx
@@ -19,11 +19,56 @@
 define Randomize {
   permuteFrom (tree,random,output) {
     CategoricalTree<#c> copy <- tree.duplicate()
-    $Hidden[tree]$
+    Generator<#c> generator <- copy `RandomCategorical:sampleWith` random
+    $Hidden[tree,random]$
     while (copy.getTotal() > 0) {
-      #c cat <- copy.locate((random.generate()*copy.getTotal().asFloat()).asInt())
+      #c cat <- generator.generate()
       \ output.append(cat)
       \ copy.decrWeight(cat)
     }
+  }
+
+  permuteFromWeight (tree,random,output) {
+    CategoricalTree<#c> copy <- tree.duplicate()
+    Generator<#c> generator <- copy `RandomCategorical:sampleWith` random
+    $Hidden[tree,random]$
+    while (copy.getTotal() > 0) {
+      #c cat <- generator.generate()
+      \ output.append(cat)
+      \ copy.setWeight(cat,0)
+    }
+  }
+}
+
+define GenerateConstant {
+  $ReadOnly[constant]$
+
+  @value Float constant
+
+  new (constant) {
+    return GenerateConstant{ constant }
+  }
+
+  generate () {
+    return constant
+  }
+}
+
+define RandomCategorical {
+  $ReadOnly[categorical,random]$
+
+  @value CategoricalReader<#c> categorical
+  @value Generator<Float> random
+
+  sampleWith (categorical,random) {
+    return RandomCategorical<#c>{ categorical, random }
+  }
+
+  generate () {
+    return categorical.locate((categorical.getTotal().asFloat()*random.generate()).asInt())
+  }
+
+  isEmpty () {
+    return categorical.getTotal() == 0
   }
 }
diff --git a/lib/math/src/token.0rx b/lib/math/src/token.0rx
--- a/lib/math/src/token.0rx
+++ b/lib/math/src/token.0rx
@@ -20,7 +20,7 @@
   $ReadOnly[mutex,toToken]$
 
   @category Mutex mutex <- SpinlockMutex.new()
-  @category SearchTree<String,Token> toToken <- SearchTree<String,Token>.new()
+  @category HashedMap<String,Token> toToken <- HashedMap<String,Token>.new()
   @category Int counter <- 0
 
   @value String string
@@ -41,6 +41,10 @@
 
   formatted () {
     return string
+  }
+
+  hashed () {
+    return value.hashed()
   }
 
   equals (x,y) {
diff --git a/lib/math/test/categorical-tree.0rt b/lib/math/test/categorical-tree.0rt
--- a/lib/math/test/categorical-tree.0rt
+++ b/lib/math/test/categorical-tree.0rt
@@ -21,7 +21,7 @@
 }
 
 unittest simpleInsertion {
-  CategoricalTree<Int> tree <- CategoricalTree<Int>.new()
+  CategoricalTree<Int> tree <- CategoricalTree<Int>.default()
       .setWeight(1,3)
       .setWeight(6,2)
       .setWeight(3,1)
@@ -29,7 +29,7 @@
       .setWeight(4,3)
       .setWeight(2,7)
 
-  Vector<Int> expected <- Vector:create<Int>()
+  Vector<Int> expected <- Vector<Int>.new()
       .push(1).push(1).push(1)
       .push(2).push(2).push(2).push(2).push(2).push(2).push(2)
       .push(3)
@@ -63,7 +63,7 @@
       .setWeight(5,1)
       .setWeight(3,0)
 
-  Vector<Int> expected <- Vector:create<Int>()
+  Vector<Int> expected <- Vector<Int>.new()
       .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)
@@ -126,8 +126,8 @@
 }
 
 unittest integrationTest { $DisableCoverage$
-  ValidatedTree<Int> tree      <- ValidatedTree<Int>.new()
-  SearchTree<Int,Int> expected <- SearchTree<Int,Int>.new()
+  ValidatedTree<Int> tree     <- ValidatedTree<Int>.new()
+  SortedMap<Int,Int> expected <- SortedMap<Int,Int>.new()
 
   Int count   <- 137
   Int maxSize <- 15
diff --git a/lib/math/test/random.0rt b/lib/math/test/random.0rt
--- a/lib/math/test/random.0rt
+++ b/lib/math/test/random.0rt
@@ -35,7 +35,29 @@
   Float value <- random.generate()
 }
 
+unittest probability {
+  Generator<Float> random <- RandomUniform.probability()
+  Float value <- random.generate()
+}
 
+unittest constant {
+  Generator<Float> random <- GenerateConstant.new(1.0)
+  Float value <- random.generate()
+}
+
+unittest categorical {
+  CategoricalTree<Float> tree <- CategoricalTree<Float>.new()
+      .setWeight(0.0,1)
+      .setWeight(1.0,1)
+      .setWeight(2.0,1)
+      .setWeight(3.0,1)
+      .setWeight(4.0,1)
+  RandomCategorical<Float> random <- tree `RandomCategorical:sampleWith` RandomUniform.probability()
+  \ Testing.checkFalse(random.isEmpty())
+  Float value <- random.generate()
+}
+
+
 testcase "negative lambda in exponential" {
   crash
   require "lambda"
@@ -69,6 +91,19 @@
 }
 
 
+testcase "sampling with empty CategoricalTree" {
+  crash
+  require "less than the total"
+}
+
+unittest categorical {
+  CategoricalTree<Float> tree <- CategoricalTree<Float>.new()
+  RandomCategorical<Float> random <- tree `RandomCategorical:sampleWith` RandomUniform.probability()
+  \ Testing.checkTrue(random.isEmpty())
+  Float value <- random.generate()
+}
+
+
 testcase "distribution sanity checks" {
   success
 }
@@ -121,10 +156,61 @@
     sum <- sum+value
   }
 
-  \ Testing.checkBetween(sum/count.asFloat(),min-0.1*(max-min),max-0.1*(max-min))
+  \ Testing.checkBetween(sum/count.asFloat(),(min+max)/2.0-0.1*(max-min),(min+max)/2.0+0.1*(max-min))
 }
 
+unittest probability { $DisableCoverage$
+  Int count <- 10000
+  $ReadOnly[count]$
 
+  Generator<Float> random <- RandomUniform.probability()
+
+  Float sum <- 0.0
+  traverse (Counter.zeroIndexed(count) -> _) {
+    Float value <- random.generate()
+    \ Testing.checkBetween(value,0.0,1.0)
+    sum <- sum+value
+  }
+
+  \ Testing.checkBetween(sum/count.asFloat(),0.4,0.6)
+}
+
+unittest constant { $DisableCoverage$
+  Int count <- 10000
+  Float constant <- 12345.0
+  $ReadOnly[count,constant]$
+
+  Generator<Float> random <- GenerateConstant.new(constant)
+
+  traverse (Counter.zeroIndexed(count) -> _) {
+    Float value <- random.generate()
+    \ Testing.checkEquals(constant,value)
+  }
+}
+
+unittest categorical { $DisableCoverage$
+  Int count <- 10000
+  CategoricalTree<Float> tree <- CategoricalTree<Float>.new()
+      .setWeight(0.0,1)
+      .setWeight(1.0,1)
+      .setWeight(2.0,1)
+      .setWeight(3.0,1)
+      .setWeight(4.0,1)
+  $ReadOnly[count,tree]$
+
+  Generator<Float> random <- tree `RandomCategorical:sampleWith` RandomUniform.probability()
+
+  Float sum <- 0.0
+  traverse (Counter.zeroIndexed(count) -> _) {
+    Float value <- random.generate()
+    \ Testing.checkBetween(value,0.0,4.0)
+    sum <- sum+value
+  }
+
+  \ Testing.checkBetween(sum/count.asFloat(),1.9,2.1)
+}
+
+
 testcase "distribution seed checks" {
   success
 }
@@ -143,8 +229,8 @@
   random2 <- random2.setSeed(123)
   Float v4 <- random2.generate()
 
-  \ Testing.checkFalse(v1 == v2)
-  \ Testing.checkFalse(v1 == v3)
+  \ Testing.checkNotEquals(v1,v2)
+  \ Testing.checkNotEquals(v1,v3)
   \ Testing.checkEquals(v3,v4)
 }
 
@@ -163,8 +249,8 @@
   random2 <- random2.setSeed(123)
   Float v4 <- random2.generate()
 
-  \ Testing.checkFalse(v1 == v2)
-  \ Testing.checkFalse(v1 == v3)
+  \ Testing.checkNotEquals(v1,v2)
+  \ Testing.checkNotEquals(v1,v3)
   \ Testing.checkEquals(v3,v4)
 }
 
@@ -183,8 +269,8 @@
   random2 <- random2.setSeed(123)
   Float v4 <- random2.generate()
 
-  \ Testing.checkFalse(v1 == v2)
-  \ Testing.checkFalse(v1 == v3)
+  \ Testing.checkNotEquals(v1,v2)
+  \ Testing.checkNotEquals(v1,v3)
   \ Testing.checkEquals(v3,v4)
 }
 
@@ -199,7 +285,7 @@
       .setWeight("b",3)
       .setWeight("c",1)
 
-  Vector<String> expected <- Vector:create<String>()
+  Vector<String> expected <- Vector<String>.new()
       .append("a")
       .append("a")
       .append("b")
@@ -207,8 +293,33 @@
       .append("b")
       .append("c")
 
-  Vector<String> output <- Vector:create<String>()
+  Vector<String> output <- Vector<String>.new()
   \ Randomize:permuteFrom(tree,RandomUniform.new(0.0,1.0),output)
+  \ Sorting:sort(output)
+
+  \ Testing.checkEquals(output.size(),expected.size())
+  traverse (Counter.zeroIndexed(output.size()) -> Int pos) {
+    \ Testing.checkEquals(output.readAt(pos),expected.readAt(pos))
+  }
+
+  \ Testing.checkEquals(tree.getWeight("a"),2)
+  \ Testing.checkEquals(tree.getWeight("b"),3)
+  \ Testing.checkEquals(tree.getWeight("c"),1)
+}
+
+unittest permuteFromWeight {
+  CategoricalTree<String> tree <- CategoricalTree<String>.new()
+      .setWeight("a",2)
+      .setWeight("b",3)
+      .setWeight("c",1)
+
+  Vector<String> expected <- Vector<String>.new()
+      .append("a")
+      .append("b")
+      .append("c")
+
+  Vector<String> output <- Vector<String>.new()
+  \ Randomize:permuteFromWeight(tree,RandomUniform.new(0.0,1.0),output)
   \ Sorting:sort(output)
 
   \ Testing.checkEquals(output.size(),expected.size())
diff --git a/lib/math/test/token.0rt b/lib/math/test/token.0rt
--- a/lib/math/test/token.0rt
+++ b/lib/math/test/token.0rt
@@ -34,3 +34,9 @@
 unittest formatted {
   \ Testing.checkEquals(Token.from("message").formatted(),"message")
 }
+
+unittest hashed {
+  \ Testing.checkNotEquals(Token.from("message").hashed(),Token.from("other").hashed())
+  \ Testing.checkNotEquals(Token.from("message").hashed(),"message".hashed())
+  \ Testing.checkEquals(Token.from("message").hashed(),Token.from("message").hashed())
+}
diff --git a/lib/math/token.0rp b/lib/math/token.0rp
--- a/lib/math/token.0rp
+++ b/lib/math/token.0rp
@@ -22,6 +22,7 @@
   defines Equals<Token>
   // LessThan is not lexicographical.
   defines LessThan<Token>
+  refines Hashed
   refines Formatted
 
   // Get a Token from the provided String.
diff --git a/lib/testing/helpers.0rp b/lib/testing/helpers.0rp
--- a/lib/testing/helpers.0rp
+++ b/lib/testing/helpers.0rp
@@ -30,6 +30,16 @@
     #x defines Equals<#x>
   (#x,#x) -> ()
 
+  // Check the values for inequality. Crashes if they are equal.
+  //
+  // Args:
+  // - #x: Actual value.
+  // - #x: Expected value.
+  @type checkNotEquals<#x>
+    #x requires Formatted
+    #x defines Equals<#x>
+  (#x,#x) -> ()
+
   // Check the value for empty. Crashes if present.
   //
   // Args:
diff --git a/lib/testing/src/helpers.0rx b/lib/testing/src/helpers.0rx
--- a/lib/testing/src/helpers.0rx
+++ b/lib/testing/src/helpers.0rx
@@ -29,6 +29,16 @@
     }
   }
 
+  checkNotEquals (x,y) { $NoTrace$
+    if (#x.equals(x,y)) {
+      fail(String.builder()
+          .append(x)
+          .append(" is equal to ")
+          .append(y)
+          .build())
+    }
+  }
+
   checkEmpty (x) { $NoTrace$
     if (present(x)) {
       scoped {
diff --git a/lib/testing/test/tests.0rt b/lib/testing/test/tests.0rt
--- a/lib/testing/test/tests.0rt
+++ b/lib/testing/test/tests.0rt
@@ -36,6 +36,25 @@
 }
 
 
+testcase "checkNotEquals success" {
+  success
+}
+
+unittest test {
+  \ Testing.checkNotEquals(12,13)
+}
+
+
+testcase "checkNotEquals fail" {
+  crash
+  require stderr "13.*13"
+}
+
+unittest test {
+  \ Testing.checkNotEquals(13,13)
+}
+
+
 testcase "checkEmpty success" {
   success
 }
diff --git a/lib/util/.zeolite-module b/lib/util/.zeolite-module
--- a/lib/util/.zeolite-module
+++ b/lib/util/.zeolite-module
@@ -15,6 +15,14 @@
 ]
 extra_files: [
   category_source {
+    source: "lib/util/src/Extension_BasicInput.cpp"
+    categories: [BasicInput]
+  }
+  category_source {
+    source: "lib/util/src/Extension_BasicOutput.cpp"
+    categories: [BasicOutput]
+  }
+  category_source {
     source: "lib/util/src/Extension_Argv.cpp"
     categories: [Argv]
   }
@@ -23,16 +31,8 @@
     categories: [MutexLock]
   }
   category_source {
-    source: "lib/util/src/Extension_SimpleInput.cpp"
-    categories: [SimpleInput]
-  }
-  category_source {
     source: "lib/util/src/Extension_SimpleMutex.cpp"
     categories: [SimpleMutex]
-  }
-  category_source {
-    source: "lib/util/src/Extension_SimpleOutput.cpp"
-    categories: [SimpleOutput]
   }
   category_source {
     source: "lib/util/src/Extension_SpinlockMutex.cpp"
diff --git a/lib/util/extra.0rp b/lib/util/extra.0rp
--- a/lib/util/extra.0rp
+++ b/lib/util/extra.0rp
@@ -19,18 +19,17 @@
 // A type with only one value.
 //
 // Notes:
+// - Get the Void value using Void.default().
 // - The built-in type with no values is the meta-type all. Void should be used
 //   when an actual value is required but no other type makes sense.
 concrete Void {
   immutable
-
-  refines Formatted
-
+  defines Default
   defines Equals<Void>
   defines LessThan<Void>
-
-  // Return the only value of type Void.
-  @type void () -> (Void)
+  refines Duplicate
+  refines Formatted
+  refines Hashed
 }
 
 // Contains either a value or an error message.
diff --git a/lib/util/helpers.0rp b/lib/util/helpers.0rp
--- a/lib/util/helpers.0rp
+++ b/lib/util/helpers.0rp
@@ -65,6 +65,39 @@
   @category equalsWith<#x,#xx>
     #xx defines Equals<#x>
   (optional Order<#x>,optional Order<#x>) -> (Bool)
+
+  // Copies references to all elements to the destination.
+  //
+  // Params:
+  // - #x: Element type managed by the container.
+  // - #c: Container type.
+  //
+  // Args:
+  // - optional Order<#x>: Element source.
+  // - #c: Output container.
+  //
+  // Returns:
+  // - #c: The container originally passed.
+  @category copyTo<#x,#c>
+    #c requires Append<#x>
+  (optional Order<#x>,#c) -> (#c)
+
+  // Duplicates all elements to the destination.
+  //
+  // Params:
+  // - #x: Element type managed by the container.
+  // - #c: Container type.
+  //
+  // Args:
+  // - optional Order<#x>: Element source.
+  // - #c: Output container.
+  //
+  // Returns:
+  // - #c: The container originally passed.
+  @category duplicateTo<#x,#c>
+    #x requires Duplicate
+    #c requires Append<#x>
+  (optional Order<#x>,#c) -> (#c)
 }
 
 // Helpers to extend functionality to the ReadAt<#x> built-in.
@@ -116,8 +149,65 @@
   @category equalsWith<#x,#xx>
     #xx defines Equals<#x>
   (ReadAt<#x>,ReadAt<#x>) -> (Bool)
+
+  // Copies references to all elements to the destination.
+  //
+  // Params:
+  // - #x: Element type managed by the container.
+  // - #c: Container type.
+  //
+  // Args:
+  // - ReadAt<#x>: Element source.
+  // - #c: Output container.
+  //
+  // Returns:
+  // - #c: The container originally passed.
+  @category copyTo<#x,#c>
+    #c requires Append<#x>
+  (ReadAt<#x>,#c) -> (#c)
+
+  // Duplicates all elements to the destination.
+  //
+  // Params:
+  // - #x: Element type managed by the container.
+  // - #c: Container type.
+  //
+  // Args:
+  // - ReadAt<#x>: Element source.
+  // - #c: Output container.
+  //
+  // Returns:
+  // - #c: The container originally passed.
+  @category duplicateTo<#x,#c>
+    #x requires Duplicate
+    #c requires Append<#x>
+  (ReadAt<#x>,#c) -> (#c)
+
+  // Iterates over the container from 0 to the end.
+  @category forwardOrder<#x> (ReadAt<#x>) -> (optional Order<#x>)
+
+  // Iterates over the container from the end to 0.
+  @category reverseOrder<#x> (ReadAt<#x>) -> (optional Order<#x>)
+
+  // Iterates over the container using the provided index order.
+  @category iterateWith<#x> (ReadAt<#x>,optional Order<Int>) -> (optional Order<#x>)
 }
 
+// Helpers to extend functionality to the Formatted built-in.
+concrete FormattedH {
+  // Attempts to convert the value to Formatted.
+  //
+  // Notes:
+  // - This is primarily meant for debugging and logging. Program logic should
+  //   not rely on the returned values.
+  // - If the value is not Formatted, a fabricated Formatted will be returned.
+  //
+  // Example:
+  //
+  //   fail(FormattedH:try(badValue))
+  @category try<#x> (optional #x) -> (Formatted)
+}
+
 // Reverse an existing LessThan<#x> comparison.
 concrete Reversed<#x|> {
   defines LessThan<#x>
@@ -138,4 +228,15 @@
 concrete AlwaysEqual {
   defines Equals<any>
   defines LessThan<any>
+}
+
+// Creates a LessThan2 from a LessThan.
+//
+// Params:
+// - #x: Object type to be compared.
+// - #xx: Comparator for #x.
+concrete AsLessThan2<#x|#xx|> {
+  #xx defines LessThan<#x>
+
+  @type new () -> (LessThan2<#x>)
 }
diff --git a/lib/util/input.0rp b/lib/util/input.0rp
--- a/lib/util/input.0rp
+++ b/lib/util/input.0rp
@@ -54,10 +54,10 @@
   @value pastEnd () -> (Bool)
 }
 
-// Simple input sources.
-concrete SimpleInput {
+// Basic input sources.
+concrete BasicInput {
   refines BlockReader<String>
 
   // Returns a BlockReader for standard input.
-  @type stdin () -> (BlockReader<String>)
+  @type stdin () -> (#self)
 }
diff --git a/lib/util/interfaces.0rp b/lib/util/interfaces.0rp
--- a/lib/util/interfaces.0rp
+++ b/lib/util/interfaces.0rp
@@ -16,14 +16,7 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-// Can be duplicated.
-@value interface Duplicate {
-  // Creates a duplicate that can be mutated without changing the original.
-  //
-  // Notes:
-  // - This does not need to be a deep copy. The only requirement is that
-  //   mutating functions can be called on the copy without counterintuitive
-  //   results. For example, if the object is a container, the duplicate could
-  //   be a copy of the structure, while still referring to the same objects.
-  duplicate () -> (#self)
+// A @value version of the LessThan @type interface.
+@value interface LessThan2<#x|> {
+  lessThan2 (#x,#x) -> (Bool)
 }
diff --git a/lib/util/output.0rp b/lib/util/output.0rp
--- a/lib/util/output.0rp
+++ b/lib/util/output.0rp
@@ -21,7 +21,7 @@
 // Params:
 // - #x: The value type to be written.
 @value interface Writer<#x|> {
-  write (#x) -> ()
+  write (#x) -> (#self)
 }
 
 // A sink that can be flushed.
@@ -31,7 +31,7 @@
 @value interface BufferedWriter<#x|> {
   refines Writer<#x>
 
-  flush () -> ()
+  flush () -> (#self)
 }
 
 // A sink that might perform a partial write.
@@ -43,27 +43,18 @@
   writeBlock (#x) -> (Int)
 }
 
-// A lazy output stream that delays writing.
-//
-// Params:
-// - #x: The value type to be written.
-concrete LazyStream<#x> {
-  refines Append<#x>
-
-  // Create a new stream.
-  @type new () -> (LazyStream<#x>)
-  // Write the data to the BufferedWriter. (Do not mutate the stream.)
-  @value writeTo (BufferedWriter<#x>) -> ()
-}
-
-// Simple output destinations.
-concrete SimpleOutput {
+// Basic output destinations.
+concrete BasicOutput {
+  refines Append<Formatted>
   refines BufferedWriter<Formatted>
 
-  // Returns a BufferedWriter for standard output.
-  @type stdout () -> (BufferedWriter<Formatted>)
-  // Returns a BufferedWriter for standard error.
-  @type stderr () -> (BufferedWriter<Formatted>)
-  // Returns a BufferedWriter that crashes with the output as the error message.
-  @type error () -> (BufferedWriter<Formatted>)
+  // Immediately writes the data and flushes the buffer.
+  @value writeNow (Formatted) -> (#self)
+
+  // Standard output writer.
+  @type stdout () -> (#self)
+  // Standard error writer.
+  @type stderr () -> (#self)
+  // Crashes with the written data as the error when flushed.
+  @type error () -> (#self)
 }
diff --git a/lib/util/src/Extension_BasicInput.cpp b/lib/util/src/Extension_BasicInput.cpp
new file mode 100644
--- /dev/null
+++ b/lib/util/src/Extension_BasicInput.cpp
@@ -0,0 +1,97 @@
+/* -----------------------------------------------------------------------------
+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]
+
+// TODO: Maybe use C++ instead.
+#include <unistd.h>
+
+#include "category-source.hpp"
+#include "Streamlined_BasicInput.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+namespace {
+extern const BoxedValue Var_stdin;
+}  // namespace
+
+struct ExtCategory_BasicInput : public Category_BasicInput {
+};
+
+struct ExtType_BasicInput : public Type_BasicInput {
+  inline ExtType_BasicInput(Category_BasicInput& p, Params<0>::Type params) : Type_BasicInput(p, params) {}
+
+  ReturnTuple Call_stdin(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("BasicInput.stdin")
+    return ReturnTuple(Var_stdin);
+  }
+};
+
+struct ExtValue_BasicInput : public Value_BasicInput {
+  inline ExtValue_BasicInput(S<const Type_BasicInput> p)
+    : Value_BasicInput(std::move(p)) {}
+
+  ReturnTuple Call_pastEnd(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("BasicInput.pastEnd")
+    std::lock_guard<std::mutex> lock(mutex);
+    return ReturnTuple(Box_Bool(zero_read));
+  }
+
+  ReturnTuple Call_readBlock(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("BasicInput.readBlock")
+    std::lock_guard<std::mutex> lock(mutex);
+    const int size = params_args.GetArg(0).AsInt();
+    if (size < 0) {
+      FAIL() << "Read size " << size << " is invalid";
+    }
+    std::string buffer(size, '\x00');
+    const int read_size = read(STDIN_FILENO, &buffer[0], size);
+    if (read_size < 0) {
+      return ReturnTuple(Box_String(""));
+    } else {
+      zero_read = read_size == 0;
+      return ReturnTuple(Box_String(buffer.substr(0, read_size)));
+    }
+  }
+
+  bool zero_read = false;
+  std::mutex mutex;
+};
+
+namespace {
+const BoxedValue Var_stdin = BoxedValue::New<ExtValue_BasicInput>(CreateType_BasicInput(Params<0>::Type()));
+}  // namespace
+
+Category_BasicInput& CreateCategory_BasicInput() {
+  static auto& category = *new ExtCategory_BasicInput();
+  return category;
+}
+
+S<const Type_BasicInput> CreateType_BasicInput(const Params<0>::Type& params) {
+  static const auto cached = S_get(new ExtType_BasicInput(CreateCategory_BasicInput(), Params<0>::Type()));
+  return cached;
+}
+
+void RemoveType_BasicInput(const Params<0>::Type& params) {}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/lib/util/src/Extension_BasicOutput.cpp b/lib/util/src/Extension_BasicOutput.cpp
new file mode 100644
--- /dev/null
+++ b/lib/util/src/Extension_BasicOutput.cpp
@@ -0,0 +1,154 @@
+/* -----------------------------------------------------------------------------
+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 <iostream>
+#include <mutex>
+#include <sstream>
+
+#include "category-source.hpp"
+#include "Streamlined_BasicOutput.hpp"
+#include "Category_Formatted.hpp"
+#include "Category_String.hpp"
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+namespace ZEOLITE_PUBLIC_NAMESPACE {
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
+
+namespace {
+extern const BoxedValue Var_stdout;
+extern const BoxedValue Var_stderr;
+extern const BoxedValue Var_error;
+}  // namespace
+
+struct ExtCategory_BasicOutput : public Category_BasicOutput {
+};
+
+struct ExtType_BasicOutput : public Type_BasicOutput {
+  inline ExtType_BasicOutput(Category_BasicOutput& p, Params<0>::Type params) : Type_BasicOutput(p, params) {}
+
+  ReturnTuple Call_error(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("BasicOutput.error")
+    return ReturnTuple(Var_error);
+  }
+
+  ReturnTuple Call_stderr(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("BasicOutput.stderr")
+    return ReturnTuple(Var_stderr);
+  }
+
+  ReturnTuple Call_stdout(const ParamsArgs& params_args) const final {
+    TRACE_FUNCTION("BasicOutput.stdout")
+    return ReturnTuple(Var_stdout);
+  }
+};
+
+namespace {
+struct Writer {
+  virtual void Write(const PrimString& message) = 0;
+  virtual void Flush() = 0;
+  virtual ~Writer() {}
+};
+}  // namespace
+
+struct ExtValue_BasicOutput : public Value_BasicOutput {
+  inline ExtValue_BasicOutput(S<const Type_BasicOutput> p, S<Writer> w)
+    : Value_BasicOutput(std::move(p)), writer(w) {}
+
+  ReturnTuple Call_append(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("BasicOutput.append")
+    return Call_write(params_args);
+  }
+
+  ReturnTuple Call_flush(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("BasicOutput.flush")
+    std::lock_guard<std::mutex> lock(mutex);
+    writer->Flush();
+    return ReturnTuple(VAR_SELF);
+  }
+
+  ReturnTuple Call_write(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("BasicOutput.write")
+    const BoxedValue& Var_arg1 = (params_args.GetArg(0));
+    std::lock_guard<std::mutex> lock(mutex);
+    writer->Write(TypeValue::Call(params_args.GetArg(0), Function_Formatted_formatted,
+                                  PassParamsArgs()).At(0).AsString());
+    return ReturnTuple(VAR_SELF);
+  }
+
+  ReturnTuple Call_writeNow(const ParamsArgs& params_args) final {
+    TRACE_FUNCTION("BasicOutput.writeNow")
+    (void) Call_write(params_args);
+    (void) Call_flush(PassParamsArgs());
+    return ReturnTuple(VAR_SELF);
+  }
+
+  std::mutex mutex;
+  const S<Writer> writer;
+};
+
+namespace {
+
+class StreamWriter : public Writer {
+ public:
+  StreamWriter(std::ostream& o) : output(o) {}
+
+  void Write(const PrimString& message) final {
+    output << message;
+  }
+
+  void Flush() final {}
+
+ private:
+  std::ostream& output;
+};
+
+class ErrorWriter : public Writer {
+ public:
+  void Write(const PrimString& message) final {
+    output << message;
+  }
+
+  void Flush() final {
+    FAIL() << output.str();
+  }
+
+  std::ostringstream output;
+};
+
+const BoxedValue Var_stdout = BoxedValue::New<ExtValue_BasicOutput>(CreateType_BasicOutput(Params<0>::Type()), S_get(new StreamWriter(std::cout)));
+const BoxedValue Var_stderr = BoxedValue::New<ExtValue_BasicOutput>(CreateType_BasicOutput(Params<0>::Type()), S_get(new StreamWriter(std::cerr)));
+const BoxedValue Var_error  = BoxedValue::New<ExtValue_BasicOutput>(CreateType_BasicOutput(Params<0>::Type()), S_get(new ErrorWriter()));
+
+}  // namespace
+
+Category_BasicOutput& CreateCategory_BasicOutput() {
+  static auto& category = *new ExtCategory_BasicOutput();
+  return category;
+}
+
+S<const Type_BasicOutput> CreateType_BasicOutput(const Params<0>::Type& params) {
+  static const auto cached = S_get(new ExtType_BasicOutput(CreateCategory_BasicOutput(), Params<0>::Type()));
+  return cached;
+}
+
+void RemoveType_BasicOutput(const Params<0>::Type& params) {}
+
+#ifdef ZEOLITE_PUBLIC_NAMESPACE
+}  // namespace ZEOLITE_PUBLIC_NAMESPACE
+using namespace ZEOLITE_PUBLIC_NAMESPACE;
+#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/lib/util/src/Extension_SimpleInput.cpp b/lib/util/src/Extension_SimpleInput.cpp
deleted file mode 100644
--- a/lib/util/src/Extension_SimpleInput.cpp
+++ /dev/null
@@ -1,97 +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]
-
-// TODO: Maybe use C++ instead.
-#include <unistd.h>
-
-#include "category-source.hpp"
-#include "Streamlined_SimpleInput.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_String.hpp"
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-namespace {
-extern const BoxedValue Var_stdin;
-}  // namespace
-
-struct ExtCategory_SimpleInput : public Category_SimpleInput {
-};
-
-struct ExtType_SimpleInput : public Type_SimpleInput {
-  inline ExtType_SimpleInput(Category_SimpleInput& p, Params<0>::Type params) : Type_SimpleInput(p, params) {}
-
-  ReturnTuple Call_stdin(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("SimpleInput.stdin")
-    return ReturnTuple(Var_stdin);
-  }
-};
-
-struct ExtValue_SimpleInput : public Value_SimpleInput {
-  inline ExtValue_SimpleInput(S<const Type_SimpleInput> p)
-    : Value_SimpleInput(std::move(p)) {}
-
-  ReturnTuple Call_pastEnd(const ParamsArgs& params_args) final {
-    TRACE_FUNCTION("SimpleInput.pastEnd")
-    std::lock_guard<std::mutex> lock(mutex);
-    return ReturnTuple(Box_Bool(zero_read));
-  }
-
-  ReturnTuple Call_readBlock(const ParamsArgs& params_args) final {
-    TRACE_FUNCTION("SimpleInput.readBlock")
-    std::lock_guard<std::mutex> lock(mutex);
-    const int size = params_args.GetArg(0).AsInt();
-    if (size < 0) {
-      FAIL() << "Read size " << size << " is invalid";
-    }
-    std::string buffer(size, '\x00');
-    const int read_size = read(STDIN_FILENO, &buffer[0], size);
-    if (read_size < 0) {
-      return ReturnTuple(Box_String(""));
-    } else {
-      zero_read = read_size == 0;
-      return ReturnTuple(Box_String(buffer.substr(0, read_size)));
-    }
-  }
-
-  bool zero_read = false;
-  std::mutex mutex;
-};
-
-namespace {
-const BoxedValue Var_stdin = BoxedValue::New<ExtValue_SimpleInput>(CreateType_SimpleInput(Params<0>::Type()));
-}  // namespace
-
-Category_SimpleInput& CreateCategory_SimpleInput() {
-  static auto& category = *new ExtCategory_SimpleInput();
-  return category;
-}
-
-S<const Type_SimpleInput> CreateType_SimpleInput(const Params<0>::Type& params) {
-  static const auto cached = S_get(new ExtType_SimpleInput(CreateCategory_SimpleInput(), Params<0>::Type()));
-  return cached;
-}
-
-void RemoveType_SimpleInput(const Params<0>::Type& params) {}
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/lib/util/src/Extension_SimpleOutput.cpp b/lib/util/src/Extension_SimpleOutput.cpp
deleted file mode 100644
--- a/lib/util/src/Extension_SimpleOutput.cpp
+++ /dev/null
@@ -1,142 +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 <iostream>
-#include <mutex>
-#include <sstream>
-
-#include "category-source.hpp"
-#include "Streamlined_SimpleOutput.hpp"
-#include "Category_Formatted.hpp"
-#include "Category_String.hpp"
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-namespace ZEOLITE_PUBLIC_NAMESPACE {
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
-
-namespace {
-extern const BoxedValue Var_stdout;
-extern const BoxedValue Var_stderr;
-extern const BoxedValue Var_error;
-}  // namespace
-
-struct ExtCategory_SimpleOutput : public Category_SimpleOutput {
-};
-
-struct ExtType_SimpleOutput : public Type_SimpleOutput {
-  inline ExtType_SimpleOutput(Category_SimpleOutput& p, Params<0>::Type params) : Type_SimpleOutput(p, params) {}
-
-  ReturnTuple Call_error(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("SimpleOutput.error")
-    return ReturnTuple(Var_error);
-  }
-
-  ReturnTuple Call_stderr(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("SimpleOutput.stderr")
-    return ReturnTuple(Var_stderr);
-  }
-
-  ReturnTuple Call_stdout(const ParamsArgs& params_args) const final {
-    TRACE_FUNCTION("SimpleOutput.stdout")
-    return ReturnTuple(Var_stdout);
-  }
-};
-
-namespace {
-struct Writer {
-  virtual void Write(const PrimString& message) = 0;
-  virtual void Flush() = 0;
-  virtual ~Writer() {}
-};
-}  // namespace
-
-struct ExtValue_SimpleOutput : public Value_SimpleOutput {
-  inline ExtValue_SimpleOutput(S<const Type_SimpleOutput> p, S<Writer> w)
-    : Value_SimpleOutput(std::move(p)), writer(w) {}
-
-  ReturnTuple Call_flush(const ParamsArgs& params_args) final {
-    TRACE_FUNCTION("SimpleOutput.flush")
-    std::lock_guard<std::mutex> lock(mutex);
-    writer->Flush();
-    return ReturnTuple();
-  }
-
-  ReturnTuple Call_write(const ParamsArgs& params_args) final {
-    TRACE_FUNCTION("SimpleOutput.write")
-    const BoxedValue& Var_arg1 = (params_args.GetArg(0));
-    std::lock_guard<std::mutex> lock(mutex);
-    writer->Write(TypeValue::Call(params_args.GetArg(0), Function_Formatted_formatted,
-                                  PassParamsArgs()).At(0).AsString());
-    return ReturnTuple();
-  }
-
-  std::mutex mutex;
-  const S<Writer> writer;
-};
-
-namespace {
-
-class StreamWriter : public Writer {
- public:
-  StreamWriter(std::ostream& o) : output(o) {}
-
-  void Write(const PrimString& message) final {
-    output << message;
-  }
-
-  void Flush() final {}
-
- private:
-  std::ostream& output;
-};
-
-class ErrorWriter : public Writer {
- public:
-  void Write(const PrimString& message) final {
-    output << message;
-  }
-
-  void Flush() final {
-    FAIL() << output.str();
-  }
-
-  std::ostringstream output;
-};
-
-const BoxedValue Var_stdout = BoxedValue::New<ExtValue_SimpleOutput>(CreateType_SimpleOutput(Params<0>::Type()), S_get(new StreamWriter(std::cout)));
-const BoxedValue Var_stderr = BoxedValue::New<ExtValue_SimpleOutput>(CreateType_SimpleOutput(Params<0>::Type()), S_get(new StreamWriter(std::cerr)));
-const BoxedValue Var_error  = BoxedValue::New<ExtValue_SimpleOutput>(CreateType_SimpleOutput(Params<0>::Type()), S_get(new ErrorWriter()));
-
-}  // namespace
-
-Category_SimpleOutput& CreateCategory_SimpleOutput() {
-  static auto& category = *new ExtCategory_SimpleOutput();
-  return category;
-}
-
-S<const Type_SimpleOutput> CreateType_SimpleOutput(const Params<0>::Type& params) {
-  static const auto cached = S_get(new ExtType_SimpleOutput(CreateCategory_SimpleOutput(), Params<0>::Type()));
-  return cached;
-}
-
-void RemoveType_SimpleOutput(const Params<0>::Type& params) {}
-
-#ifdef ZEOLITE_PUBLIC_NAMESPACE
-}  // namespace ZEOLITE_PUBLIC_NAMESPACE
-using namespace ZEOLITE_PUBLIC_NAMESPACE;
-#endif  // ZEOLITE_PUBLIC_NAMESPACE
diff --git a/lib/util/src/extra.0rx b/lib/util/src/extra.0rx
--- a/lib/util/src/extra.0rx
+++ b/lib/util/src/extra.0rx
@@ -19,8 +19,16 @@
 define Void {
   @category Void singleton <- Void{ }
 
+  default () {
+    return singleton
+  }
+
+  duplicate () {
+    return singleton
+  }
+
   formatted () {
-    return "void"
+    return "Void"
   }
 
   equals (_,_) {
@@ -28,11 +36,11 @@
   }
 
   lessThan (_,_) {
-    return true
+    return false
   }
 
-  void () {
-    return singleton
+  hashed () {
+    return 123456789
   }
 }
 
diff --git a/lib/util/src/helpers.0rx b/lib/util/src/helpers.0rx
--- a/lib/util/src/helpers.0rx
+++ b/lib/util/src/helpers.0rx
@@ -58,6 +58,22 @@
     }
     return !present(xx) && !present(yy)
   }
+
+  copyTo (input,output) (original) {
+    original <- output
+    $Hidden[original]$
+    traverse (input -> #x value) {
+      \ output.append(value)
+    }
+  }
+
+  duplicateTo (input,output) (original) {
+    original <- output
+    $Hidden[original]$
+    traverse (input -> #x value) {
+      \ output.append(value.duplicate())
+    }
+  }
 }
 
 define ReadAtH {
@@ -66,7 +82,7 @@
   }
 
   lessThanWith (x,y) {
-    traverse (Counter.zeroIndexed(Ranges:min(x.size(),y.size())) -> Int i) {
+    traverse (`Counter.zeroIndexed` (x.size() `Ranges:min` y.size()) -> Int i) {
       $ReadOnly[i]$
       if (x.readAt(i) `#xx.lessThan` y.readAt(i)) {
         return true
@@ -85,7 +101,7 @@
     if (x.size() != y.size()) {
       return false
     }
-    traverse (Counter.zeroIndexed(x.size()) -> Int i) {
+    traverse (`Counter.zeroIndexed` x.size() -> Int i) {
       $ReadOnly[i]$
       if (!(x.readAt(i) `#xx.equals` y.readAt(i))) {
         return false
@@ -93,8 +109,94 @@
     }
     return true
   }
+
+  copyTo (input,output) (original) {
+    original <- output
+    $Hidden[original]$
+    traverse (`Counter.zeroIndexed` input.size() -> Int i) {
+      \ output.append(input.readAt(i))
+    }
+  }
+
+  duplicateTo (input,output) (original) {
+    original <- output
+    $Hidden[original]$
+    traverse (`Counter.zeroIndexed` input.size() -> Int i) {
+      \ output.append(input.readAt(i).duplicate())
+    }
+  }
+
+  forwardOrder (input) {
+    return input `iterateWith` `Counter.zeroIndexed` input.size()
+  }
+
+  reverseOrder (input) {
+    return input `iterateWith` `Counter.revZeroIndexed` input.size()
+  }
+
+  iterateWith (input,order) {
+    return input `IndexOrder:iterateWith` order
+  }
 }
 
+define FormattedH {
+  try (x) {
+    if (present(x)) {
+      scoped {
+        optional Formatted formatted <- reduce<#x,Formatted>(x)
+      } in if (present(formatted)) {
+        return require(formatted)
+      } else {
+        return String.builder()
+            .append(typename<#x>())
+            .append("{?}")
+            .build()
+      }
+    } else {
+        return String.builder()
+            .append(typename<#x>())
+            .append("{empty}")
+            .build()
+    }
+  }
+}
+
+concrete IndexOrder<#x> {
+  refines Order<#x>
+
+  @category iterateWith<#x> (ReadAt<#x>,optional Order<Int>) -> (optional Order<#x>)
+}
+
+define IndexOrder {
+  $ReadOnly[container]$
+
+  @value ReadAt<#x> container
+  @value Order<Int> index
+
+  iterateWith (container,index) {
+    if (`present` index) {
+      return IndexOrder<#x>{ container, `require` index }
+    } else {
+      return empty
+    }
+  }
+
+  get () {
+    return container.readAt(index.get())
+  }
+
+  next () {
+    scoped {
+      optional Order<Int> index2 <- index.next()
+    } in if (`present` index2) {
+      index <- `require` index2
+      return self
+    } else {
+      return empty
+    }
+  }
+}
+
 define Reversed {
   lessThan (x,y) {
     return y `#x.lessThan` x
@@ -118,5 +220,17 @@
 
   lessThan (_,_) {
     return false
+  }
+}
+
+define AsLessThan2 {
+  refines LessThan2<#x>
+
+  new () {
+    return #self{ }
+  }
+
+  lessThan2 (x,y) {
+    return x `#xx.lessThan` y
   }
 }
diff --git a/lib/util/src/input.0rx b/lib/util/src/input.0rx
--- a/lib/util/src/input.0rx
+++ b/lib/util/src/input.0rx
@@ -32,14 +32,15 @@
     return builder.build()
   }
 
-  readNextLine () {
+  readNextLine () (line) {
+    line <- ""
     while (true) {
       Int newline <- findNewline()
       if (newline < 0) {
         if (reader.pastEnd()) {
-          cleanup {
-            buffer <- ""
-          } in return buffer
+          line <- buffer
+          buffer <- ""
+          break
         } else {
           buffer <- buffer+reader.readBlock($ExprLookup[BLOCK_READ_SIZE]$)
         }
@@ -47,7 +48,6 @@
         return takeLine(newline)
       }
     }
-    return ""
   }
 
   pastEnd () {
diff --git a/lib/util/src/output.0rx b/lib/util/src/output.0rx
deleted file mode 100644
--- a/lib/util/src/output.0rx
+++ /dev/null
@@ -1,45 +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]
-
-define LazyStream {
-  @value optional #x value
-  @value optional LazyStream<#x> next
-
-  new () {
-    return LazyStream<#x>{ empty, empty }
-  }
-
-  append (value2) {
-    return LazyStream<#x>{ value2, self }
-  }
-
-  @value recursive (Writer<#x>) -> ()
-  recursive (writer) {
-    if (present(next)) {
-      \ require(next).recursive(writer)
-    }
-    if (present(value)) {
-      \ writer.write(require(value))
-    }
-  }
-
-  writeTo (writer) {
-    \ recursive(writer)
-    \ writer.flush()
-  }
-}
diff --git a/lib/util/src/parse.0rx b/lib/util/src/parse.0rx
--- a/lib/util/src/parse.0rx
+++ b/lib/util/src/parse.0rx
@@ -129,10 +129,6 @@
 
     }
 
-    if (present(curr)) {
-      return autoFail<Float>(require(curr).get())
-    }
-
     if (needExpDigits) {
       return ErrorOr:error("missing digits in Float exponent")
     }
diff --git a/lib/util/test/counters.0rt b/lib/util/test/counters.0rt
--- a/lib/util/test/counters.0rt
+++ b/lib/util/test/counters.0rt
@@ -31,6 +31,12 @@
   \ Testing.checkEquals(index,10)
 }
 
+unittest zeroIndexedEmpty {
+  traverse (Counter.zeroIndexed(0) -> Int i) {
+    fail("not empty")
+  }
+}
+
 unittest revZeroIndexed {
   Int index <- 10
 
@@ -42,6 +48,12 @@
   \ Testing.checkEquals(index,0)
 }
 
+unittest revZeroIndexedEmpty {
+  traverse (Counter.revZeroIndexed(0) -> Int i) {
+    fail("not empty")
+  }
+}
+
 unittest unlimitedCount {
   Int index <- 0
 
@@ -64,6 +76,12 @@
   }
 
   \ Testing.checkEquals(index,10)
+}
+
+unittest timesEmpty {
+  traverse ("message" `Repeat:times` 0 -> _) {
+    fail("not empty")
+  }
 }
 
 unittest unlimitedValue {
diff --git a/lib/util/test/extra.0rt b/lib/util/test/extra.0rt
--- a/lib/util/test/extra.0rt
+++ b/lib/util/test/extra.0rt
@@ -116,3 +116,23 @@
   \ value.convertError()
 }
 
+
+testcase "Void tests" {
+  success
+}
+
+unittest formatted {
+  \ Testing.checkEquals(Void.default().formatted(),"Void")
+}
+
+unittest lessThan {
+  \ Testing.checkFalse(Void.default() `Void.lessThan` Void.default())
+}
+
+unittest equals {
+  \ Testing.checkTrue(Void.default() `Void.equals` Void.default())
+}
+
+unittest hashed {
+  \ Testing.checkEquals(Void.default().duplicate().hashed(),Void.default().hashed())
+}
diff --git a/lib/util/test/helpers.0rt b/lib/util/test/helpers.0rt
--- a/lib/util/test/helpers.0rt
+++ b/lib/util/test/helpers.0rt
@@ -137,3 +137,229 @@
   \ Testing.checkTrue("ac" `AlwaysEqual.equals` "b")
   \ Testing.checkTrue("a"  `AlwaysEqual.equals` "b")
 }
+
+
+testcase "iteration tests" {
+  success
+}
+
+unittest orderHCopyTo {
+  ValueCheck output <- ValueCheck.new(10)
+  \ ValueOrder.wrap(false,Counter.zeroIndexed(10)) `OrderH:copyTo` output
+  \ output.checkEnd()
+}
+
+unittest orderHDuplicateTo {
+  ValueCheck output <- ValueCheck.new(10)
+  \ ValueOrder.wrap(true,Counter.zeroIndexed(10)) `OrderH:duplicateTo` output
+  \ output.checkEnd()
+}
+
+unittest readAtHCopyTo {
+  ValueCheck output <- ValueCheck.new(10)
+  \ ValueSource.new(false,10) `ReadAtH:copyTo` output
+  \ output.checkEnd()
+}
+
+unittest readAtHDuplicateTo {
+  ValueCheck output <- ValueCheck.new(10)
+  \ ValueSource.new(true,10) `ReadAtH:duplicateTo` output
+  \ output.checkEnd()
+}
+
+unittest readAtHForwardOrder {
+  ValueSource source <- ValueSource.new(false,10)
+  Int expected <- 0
+  traverse (`ReadAtH:forwardOrder` source -> Value actual) {
+    \ Testing.checkEquals(actual.get(),expected)
+  } update {
+    expected <- expected+1
+  }
+  \ Testing.checkEquals(expected,10)
+}
+
+unittest readAtHForwardOrderEmpty {
+  ValueSource source <- ValueSource.new(false,0)
+  traverse (`ReadAtH:forwardOrder` source -> _) {
+    fail("not empty")
+  }
+}
+
+unittest readAtHReverseOrder {
+  ValueSource source <- ValueSource.new(false,10)
+  Int expected <- 9
+  traverse (`ReadAtH:reverseOrder` source -> Value actual) {
+    \ Testing.checkEquals(actual.get(),expected)
+  } update {
+    expected <- expected-1
+  }
+  \ Testing.checkEquals(expected,-1)
+}
+
+unittest readAtHReverseOrderEmpty {
+  ValueSource source <- ValueSource.new(false,0)
+  traverse (`ReadAtH:reverseOrder` source -> _) {
+    fail("not empty")
+  }
+}
+
+unittest readAtHIterateWith {
+  ValueSource source <- ValueSource.new(false,10)
+  Int expected <- 0
+  traverse (source `ReadAtH:iterateWith` `Counter.zeroIndexed` 5 -> Value actual) {
+    \ Testing.checkEquals(actual.get(),expected)
+  } update {
+    expected <- expected+1
+  }
+  \ Testing.checkEquals(expected,5)
+}
+
+concrete ValueOrder {
+  refines Order<Value>
+
+  @type wrap (Bool,optional Order<Int>) -> (optional Order<Value>)
+}
+
+define ValueOrder {
+  $ReadOnly[original]$
+
+  @value Bool original
+  @value Order<Int> order
+
+  wrap (original,order) {
+    if (!present(order)) {
+      return empty
+    } else {
+      return ValueOrder{ original, require(order) }
+    }
+  }
+
+  next () {
+    optional Order<Int> order2 <- order.next()
+    if (present(order2)) {
+      order <- require(order2)
+      return self
+    } else {
+      return empty
+    }
+  }
+
+  get () {
+    return Value.new(original,order.get())
+  }
+}
+
+concrete ValueCheck {
+  // Must append the next value in the sequence [0,size).
+  refines Append<Value>
+
+  @type new (Int) -> (ValueCheck)
+  @value checkEnd () -> ()
+}
+
+define ValueCheck {
+  $ReadOnly[target]$
+
+  @value Int current
+  @value Int target
+
+  new (target) {
+    return ValueCheck{ 0, target }
+  }
+
+  checkEnd () {
+    \ Testing.checkEquals(current,target)
+  }
+
+  append (x) {
+    \ Testing.checkEquals(x.get(),current)
+    current <- current+1
+    return self
+  }
+}
+
+concrete Value {
+  // Crashes if "original" is already false.
+  refines Duplicate
+
+  // Args:
+  // - Bool: Whether or not the value is "original".
+  // - Int: Value to return from get().
+  @type new (Bool,Int) -> (Value)
+
+  // Crashes if "original" is true.
+  @value get () -> (Int)
+}
+
+define Value {
+  $ReadOnly[original,value]$
+
+  @value Bool original
+  @value Int value
+
+  new (original,value) {
+    return Value{ original, value }
+  }
+
+  get () {
+    if (original) {
+      fail("not duplicated")
+    }
+    return value
+  }
+
+  duplicate () {
+    if (!original) {
+      fail("already duplicated")
+    }
+    return Value{ false, value }
+  }
+}
+
+concrete ValueSource {
+  // readAt(i) returns Value.new(original,i).
+  refines ReadAt<Value>
+
+  // Args:
+  // - Bool: Whether or not to mark new values as "original".
+  // - Int: Total size.
+  @type new (Bool,Int) -> (ValueSource)
+}
+
+define ValueSource {
+  $ReadOnly[original,size]$
+
+  @value Bool original
+  @value Int size
+
+  new (original,size) {
+    return ValueSource{ original, size }
+  }
+
+  size () {
+    return size
+  }
+
+  readAt (i) {
+    \ Testing.checkBetween(i,0,size-1)
+    return Value.new(original,i)
+  }
+}
+
+
+testcase "formatting tests" {
+  success
+}
+
+unittest emptyValue {
+  \ Testing.checkEquals(FormattedH:try<Char>(empty).formatted(),"Char{empty}")
+  \ Testing.checkEquals(FormattedH:try(empty).formatted(),"all{empty}")
+}
+
+unittest formattedValue {
+  \ Testing.checkEquals(FormattedH:try(123).formatted(),"123")
+}
+
+unittest nonFormattedValue {
+  \ Testing.checkEquals(FormattedH:try(String.builder()).formatted(),"[Append<Formatted>&Build<String>]{?}")
+}
diff --git a/lib/util/test/input.0rt b/lib/util/test/input.0rt
--- a/lib/util/test/input.0rt
+++ b/lib/util/test/input.0rt
@@ -95,5 +95,5 @@
 }
 
 unittest test {
-  \ Testing.checkEquals(TextReader.readAll(SimpleInput.stdin()),"")
+  \ Testing.checkEquals(TextReader.readAll(BasicInput.stdin()),"")
 }
diff --git a/lib/util/test/output.0rt b/lib/util/test/output.0rt
--- a/lib/util/test/output.0rt
+++ b/lib/util/test/output.0rt
@@ -18,31 +18,43 @@
 
 testcase "stdout writer" {
   success
-  require stdout "message"
+  require stdout "message1"
+  require stdout "message2"
+  require stdout "message3"
   exclude stderr "message"
 }
 
 unittest test {
-  \ LazyStream<Formatted>.new().append("message").writeTo(SimpleOutput.stdout())
+  \ BasicOutput.stdout().write("message1\n")
+  \ BasicOutput.stdout().append("message2\n")
+  \ BasicOutput.stdout().writeNow("message3\n")
 }
 
 
 testcase "stderr writer" {
   success
-  require stderr "message"
+  require stderr "message1"
+  require stderr "message2"
+  require stderr "message3"
   exclude stdout "message"
 }
 
 unittest test {
-  \ LazyStream<Formatted>.new().append("message").writeTo(SimpleOutput.stderr())
+  \ BasicOutput.stderr().write("message1\n")
+  \ BasicOutput.stderr().append("message2\n")
+  \ BasicOutput.stderr().writeNow("message3\n")
 }
 
 
 testcase "error writer" {
   crash
-  require "message"
+  require "message1"
+  require "message2"
+  require "message3"
 }
 
 unittest test {
-  \ LazyStream<Formatted>.new().append("message").writeTo(SimpleOutput.error())
+  \ BasicOutput.error().write("message1\n")
+  \ BasicOutput.error().append("message2\n")
+  \ BasicOutput.error().writeNow("message3\n")
 }
diff --git a/lib/util/test/parse.0rt b/lib/util/test/parse.0rt
--- a/lib/util/test/parse.0rt
+++ b/lib/util/test/parse.0rt
@@ -108,6 +108,14 @@
   \ UtilTesting.checkError(ParseChars.float("123E5Q"))
 }
 
+unittest floatBadCharAfterDigit {
+  \ UtilTesting.checkError(ParseChars.float("123X"))
+}
+
+unittest floatMissingExponentDigits {
+  \ UtilTesting.checkError(ParseChars.float("123E"))
+}
+
 unittest int {
   \ UtilTesting.checkSuccess(ParseChars.int("1234"),1234)
 }
diff --git a/src/Compilation/CompilerState.hs b/src/Compilation/CompilerState.hs
--- a/src/Compilation/CompilerState.hs
+++ b/src/Compilation/CompilerState.hs
@@ -43,6 +43,7 @@
   csAddUsed,
   csAddVariable,
   csAllFilters,
+  csCanForward,
   csCheckValueInit,
   csCheckVariableInit,
   csClearOutput,
@@ -151,6 +152,7 @@
   ccGetNoTrace :: a -> m Bool
   ccAddTrace :: a -> String -> m a
   ccGetTraces :: a -> m [String]
+  ccCanForward :: a -> [ParamName] -> [VariableName] -> m Bool
 
 data MemberValue c =
   MemberValue {
@@ -359,6 +361,9 @@
 
 csAddTrace :: CompilerContext c m s a => String -> CompilerState a m ()
 csAddTrace t = fmap (\x -> ccAddTrace x t) get >>= lift >>= put
+
+csCanForward :: CompilerContext c m s a => [ParamName] -> [VariableName] -> CompilerState a m Bool
+csCanForward ps as = fmap (\x -> ccCanForward x ps as) get >>= lift
 
 data CompiledData s =
   CompiledData {
diff --git a/src/Compilation/ProcedureContext.hs b/src/Compilation/ProcedureContext.hs
--- a/src/Compilation/ProcedureContext.hs
+++ b/src/Compilation/ProcedureContext.hs
@@ -89,7 +89,8 @@
     _pcExprMap :: ExprMap c,
     _pcReservedMacros :: [(MacroName,[c])],
     _pcNoTrace :: Bool,
-    _pcTraces :: [String]
+    _pcTraces :: [String],
+    _pcParentCall :: Maybe (Positional ParamName,Positional VariableName)
   }
 
 $(makeLenses ''ProcedureContext)
@@ -154,6 +155,9 @@
       tryMergeFrom fs f = do
         mapCompilerM_ (tryMergeFunc f) fs
         return f
+      tryMergeTo fs f = do
+        mapCompilerM_ (flip tryMergeFunc f) fs
+        return f
       tryMergeFunc f1 f2 = do
         f1' <- parsedToFunctionType f1
         f2' <- parsedToFunctionType f2
@@ -161,14 +165,21 @@
         allFilters <- ccAllFilters ctx
         silenceErrorsM $ checkFunctionConvert r allFilters Map.empty f2' f1'
       getFunction t0 t2 = reduceMergeTree getFromAny getFromAll (getFromSingle t0) t2
-      getFromAny _ =
-        compilerErrorM $ "Use explicit type conversion to call " ++ show n ++ " from " ++ show t
+      getFromAny fs = do
+        let (Just t') = t  -- #self will never be a union.
+        fs2 <- fmap concat (collectAllM fs) <!! "Function " ++ show n ++ " is not available for type " ++ show t' ++ formatFullContextBrace c
+        case Map.toList $ Map.fromList $ map (\f -> (sfType f,sfContext f)) fs2 of
+             -- For unions, we want the most general rather than the least
+             -- general. Since the top level finds the least general, we can
+             -- only return one match here.
+             [_] -> fmap (:[]) $ collectFirstM $ map (tryMergeTo fs2) fs2 ++ [multipleMatchError t' fs2]
+             [] -> compilerErrorM $ "Function " ++ show n ++ " is not available for type " ++ show t' ++ formatFullContextBrace c
+             cs -> "Use an explicit conversion to call " ++ show n ++ " for type " ++ show t' ++ formatFullContextBrace c !!>
+               mapErrorsM (map (\(t2,c2) -> "Function " ++ show n ++ " in " ++ show t2 ++ formatFullContextBrace c2) cs)
       getFromAll fs = do
-        t' <- case t of
-                   Just t2 -> return t2
-                   Nothing -> fmap (singleType . JustTypeInstance) $ ccSelfType ctx
+        let (Just t') = t  -- #self will never be an intersection.
         collectFirstM_ fs <!!
-          "Function " ++ show n ++ " not available for type " ++ show t' ++ formatFullContextBrace c
+          "Function " ++ show n ++ " is not available for type " ++ show t' ++ formatFullContextBrace c
         fmap concat $ collectAnyM fs
       getFromSingle t0 (JustParamName _ p) = do
         fa <- ccAllFilters ctx
@@ -224,6 +235,8 @@
       validateTypeInstanceForCall r allFilters t'
       -- Check initializer types.
       ms <- fmap Positional $ mapCompilerM (subSingle pa) (ctx ^. pcMembers)
+      -- Do a size comparison first so that the error message is readable.
+      processPairs_ alwaysPair (fmap mvType ms) ts
       processPairs_ (checkInit r allFilters) ms (Positional $ zip ([1..] :: [Int]) $ pValues ts)
       return ()
       where
@@ -254,10 +267,7 @@
     (VariableValue c2 s t _) <- ccGetVariable ctx v
     return $ ctx & pcVariables %~ Map.insert n (VariableValue c2 s t (VariableReadOnly c))
   ccSetDeferred ctx v@(UsedVariable c n) = do
-    (VariableValue c2 s t r) <- ccGetVariable ctx v
-    when (s /= LocalScope) $
-      compilerErrorM $ "Variable " ++ show n ++ formatFullContextBrace c2 ++
-        " cannot be marked as deferred because it is not locally scoped" ++ formatFullContextBrace c
+    (VariableValue c2 _ t r) <- ccGetVariable ctx v
     case r of
          VariableReadOnly c3 -> compilerErrorM $ "Variable " ++ show n ++ formatFullContextBrace c2 ++
                                   " cannot be marked as deferred " ++ formatFullContextBrace c ++
@@ -309,7 +319,8 @@
     combineParallel (da1,ValidateNames ns ts ra1,j1) (da2,ValidateNames _ _ ra2,j2) = (branchDeferred [da1,da2],ValidateNames ns ts $ branchDeferred [ra1,ra2],min j1 j2)
     combineParallel (da1,r@(ValidatePositions _),j1) (da2,_,j2) = (branchDeferred [da1,da2],r,min j1 j2)
     combineParallel (da1,_,j1) (da2,r@(ValidatePositions _),j2) = (branchDeferred [da1,da2],r,min j1 j2)
-  ccInheritDeferred ctx ds = return $ ctx & pcDeferred .~ ds
+  ccInheritDeferred ctx ds = return $ ctx & pcDeferred .~ deferred where
+    deferred = (ctx ^. pcDeferred) `followDeferred` ds
   ccInheritUsed ctx ctx2 = return $ ctx & pcUsedVars <>~ (ctx2 ^. pcUsedVars)
   ccRegisterReturn ctx c vs = do
     returns <- check (ctx ^. pcReturns)
@@ -401,6 +412,16 @@
   ccAddTrace ctx "" = return ctx
   ccAddTrace ctx t = return $ ctx & pcTraces <>~ [t]
   ccGetTraces = return . (^. pcTraces)
+  ccCanForward ctx ps as = handle (ctx ^. pcParentCall) where
+    handle Nothing = return False
+    handle (Just (ps0,as0)) = collectFirstM [checkMatch ps0 as0,return False]
+    checkMatch ps0 as0 = do
+      processPairs_ equalOrError ps0 (Positional ps)
+      processPairs_ equalOrError as0 (Positional as)
+      return True
+    equalOrError x y
+      | x == y    = return ()
+      | otherwise = emptyErrorM
 
 updateReturnVariables :: (Show c, CollectErrorsM m) =>
   (Map.Map VariableName (VariableValue c)) ->
diff --git a/src/CompilerCxx/CategoryContext.hs b/src/CompilerCxx/CategoryContext.hs
--- a/src/CompilerCxx/CategoryContext.hs
+++ b/src/CompilerCxx/CategoryContext.hs
@@ -85,7 +85,8 @@
       _pcExprMap = em,
       _pcReservedMacros = [],
       _pcNoTrace = False,
-      _pcTraces = []
+      _pcTraces = [],
+      _pcParentCall = Nothing
     }
 
 getProcedureContext :: (Show c, CollectErrorsM m) =>
@@ -145,10 +146,18 @@
       _pcExprMap = em,
       _pcReservedMacros = [],
       _pcNoTrace = False,
-      _pcTraces = []
+      _pcTraces = [],
+      _pcParentCall = parentCall
     }
   where
     pairOutput (PassedValue c1 t2) (OutputValue c2 n2) = return $ (n2,PassedValue (c2++c1) t2)
+    psList = map vpParam $ pValues ps1
+    asList = sequence $ map maybeArgName $ pValues $ avNames as2
+    maybeArgName (InputValue _ n) = Just n
+    maybeArgName _                = Nothing
+    parentCall = case asList of
+                      Just as3 -> Just (Positional psList,Positional as3)
+                      Nothing  -> Nothing
 
 getMainContext :: CollectErrorsM m => CategoryMap c -> ExprMap c -> m (ProcedureContext c)
 getMainContext tm em = return $ ProcedureContext {
@@ -177,5 +186,6 @@
     _pcExprMap = em,
     _pcReservedMacros = [],
     _pcNoTrace = False,
-    _pcTraces = []
+    _pcTraces = [],
+    _pcParentCall = Nothing
   }
diff --git a/src/CompilerCxx/CxxFiles.hs b/src/CompilerCxx/CxxFiles.hs
--- a/src/CompilerCxx/CxxFiles.hs
+++ b/src/CompilerCxx/CxxFiles.hs
@@ -410,10 +410,12 @@
       return $ onlyCode "};"
     ] where
       members = filter ((== CategoryScope). dmScope) $ dcMembers d
+
   defineConcreteType fs t = concatM [
       return $ onlyCode $ "struct " ++ className ++ " : public " ++ typeBase ++ ", std::enable_shared_from_this<" ++ className ++ "> {",
       fmap indentCompiled $ inlineTypeConstructor t,
       fmap indentCompiled $ inlineTypeDestructor False t,
+      fmap indentCompiled $ inlineTypeParamSelf t,
       return declareTypeOverrides,
       fmap indentCompiled $ concatM $ map (declareProcedure t False) fs,
       return $ indentCompiled $ createParams $ getCategoryParams t,
@@ -425,6 +427,7 @@
   defineConcreteValue r params fs t d = concatM [
       return $ onlyCode $ "struct " ++ valueName (getCategoryName t) ++ " : public " ++ valueBase ++ " {",
       fmap indentCompiled $ inlineValueConstructor t d,
+      fmap indentCompiled $ inlineValueParamSelf t,
       fmap indentCompiled $ inlineFlatCleanup d,
       return declareValueOverrides,
       fmap indentCompiled $ concatM $ map (declareProcedure t False) fs,
@@ -446,10 +449,12 @@
       return $ onlyCode $ "  virtual inline ~" ++ categoryName (getCategoryName t) ++ "() {}",
       return $ onlyCode "};"
     ]
+
   defineAbstractType t = concatM [
       return $ onlyCode $ "struct " ++ className ++ " : public " ++ typeBase ++ ", std::enable_shared_from_this<" ++ className ++ "> {",
       fmap indentCompiled $ inlineTypeConstructor t,
       fmap indentCompiled $ inlineTypeDestructor True t,
+      fmap indentCompiled $ inlineTypeParamSelf t,
       return declareTypeOverrides,
       fmap indentCompiled $ concatM $ map (declareProcedure t True) $ filter ((== TypeScope). sfScope) $ getCategoryFunctions t,
       return $ indentCompiled $ createParams $ getCategoryParams t,
@@ -457,9 +462,11 @@
       return $ onlyCode "};"
     ] where
       className = typeName (getCategoryName t)
+
   defineAbstractValue t = concatM [
       return $ onlyCode $ "struct " ++ valueName (getCategoryName t) ++ " : public " ++ valueBase ++ " {",
       fmap indentCompiled $ abstractValueConstructor t,
+      fmap indentCompiled $ inlineValueParamSelf t,
       return declareValueOverrides,
       fmap indentCompiled $ concatM $ map (declareProcedure t True) $ filter ((== ValueScope). sfScope) $ getCategoryFunctions t,
       return $ onlyCode $ "  virtual inline ~" ++ valueName (getCategoryName t) ++ "() {}",
@@ -608,7 +615,7 @@
     let argParent = "S<const " ++ typeName (getCategoryName t) ++ "> p"
     let argsPassed = "const ParamsArgs& params_args"
     let allArgs = intercalate ", " [argParent,argsPassed]
-    let initParent = "parent(p)"
+    let initParent = "parent(std::move(p))"
     let initArgs = map (\(i,m) -> variableName (dmName m) ++ "(" ++ unwrappedArg i m ++ ")") $ zip ([0..] :: [Int]) members
     let allInit = intercalate ", " $ initParent:initArgs
     return $ onlyCode $ "inline " ++ valueName (getCategoryName t) ++ "(" ++ allArgs ++ ") : " ++ allInit ++ " {}" where
@@ -618,7 +625,7 @@
   abstractValueConstructor t = do
     let argParent = "S<const " ++ typeName (getCategoryName t) ++ "> p"
     let allArgs = intercalate ", " [argParent]
-    let initParent = "parent(p)"
+    let initParent = "parent(std::move(p))"
     let allInit = initParent
     return $ onlyCode $ "inline " ++ valueName (getCategoryName t) ++ "(" ++ allArgs ++ ") : " ++ allInit ++ " {}"
 
@@ -629,12 +636,25 @@
     let allArgs = intercalate ", " [argParent,paramsPassed]
     let allInit = typeName (getCategoryName t) ++ "(p, params)"
     return $ onlyCode $ "inline " ++ typeCustom (getCategoryName t) ++ "(" ++ allArgs ++ ") : " ++ allInit ++ " {}"
+
   customValueConstructor t = do
     let argParent = "S<const " ++ typeName (getCategoryName t) ++ "> p"
     let argsPassed = "const ParamsArgs& params_args"
     let allArgs = intercalate ", " [argParent,argsPassed]
     let allInit = valueName (getCategoryName t) ++ "(std::move(p))"
     return $ onlyCode $ "inline " ++ valueCustom (getCategoryName t) ++ "(" ++ allArgs ++ ") : " ++ allInit ++ " {}"
+
+  inlineTypeParamSelf t = return $ onlyCodes [
+      "inline S<const " ++ typeName (getCategoryName t) ++ "> Param_self() const {",
+      "  return shared_from_this();",
+      "}"
+    ]
+
+  inlineValueParamSelf t = return $ onlyCodes [
+      "inline S<const " ++ typeName (getCategoryName t) ++ "> Param_self() const {",
+      "  return parent;",
+      "}"
+    ]
 
   allowTestsOnly
     | testing   = (testsOnlySourceGuard ++)
diff --git a/src/CompilerCxx/Procedure.hs b/src/CompilerCxx/Procedure.hs
--- a/src/CompilerCxx/Procedure.hs
+++ b/src/CompilerCxx/Procedure.hs
@@ -704,16 +704,15 @@
                                  show t2 ++ formatFullContextBrace c
       glueInfix t1 t2 e3 o2 e4 =
         UnboxedPrimitive t2 $ "(" ++ useAsUnboxed t1 e3 ++ ")" ++ o2 ++ "(" ++ useAsUnboxed t1 e4 ++ ")"
-  transform e (ConvertedCall c t f) = do
+  transform e (TypeConversion c t) = do
     (Positional ts,e') <- e
     t' <- requireSingle c ts
     r <- csResolver
     fa <- csAllFilters
-    let vt = ValueType RequiredValue $ singleType $ JustTypeInstance t
+    let vt = ValueType (vtRequired t') t
     (lift $ checkValueAssignment r fa t' vt) <??
-      "In converted call at " ++ formatFullContext c
-    f' <- lookupValueFunction vt f
-    compileFunctionCall (Just $ useAsUnwrapped e') f' f
+      "In explicit type conversion at " ++ formatFullContext c
+    return (Positional [vt],e')
   transform e (ValueCall c f) = do
     (Positional ts,e') <- e
     t' <- requireSingle c ts
@@ -735,10 +734,10 @@
                         CompilerContext c m [String] a) =>
   ValueType -> FunctionCall c -> CompilerState a m (ScopedFunction c)
 lookupValueFunction (ValueType WeakValue t) (FunctionCall c _ _ _) =
-  compilerErrorM $ "Use strong to convert " ++ show t ++
+  compilerErrorM $ "Use strong to convert weak " ++ show t ++
                         " to optional first" ++ formatFullContextBrace c
 lookupValueFunction (ValueType OptionalValue t) (FunctionCall c _ _ _) =
-  compilerErrorM $ "Use require to convert " ++ show t ++
+  compilerErrorM $ "Use require to convert optional " ++ show t ++
                         " to required first" ++ formatFullContextBrace c
 lookupValueFunction (ValueType RequiredValue t) (FunctionCall c n _ _) =
   csGetTypeFunction c (Just t) n
@@ -778,11 +777,18 @@
                                                     formatFullContextBrace c
   csAddRequired $ Set.unions $ map categoriesFromTypes [t']
   csAddRequired $ Set.fromList [sfType f']
-  t2 <- expandGeneralInstance t'
-  compileFunctionCall (Just t2) f' f
+  same <- maybeSingleType t' >>= checkSame
+  t2 <- if same
+           then return Nothing
+           else fmap Just $ expandGeneralInstance t'
+  compileFunctionCall t2 f' f
+  where
+    maybeSingleType = lift . tryCompilerM . matchOnlyLeaf
+    checkSame (Just (JustTypeInstance t2)) = csSameType t2
+    checkSame _ = return False
 compileExpressionStart (UnqualifiedCall c f@(FunctionCall _ n _ _)) = do
   ctx <- get
-  f' <- lift $ collectFirstM [tryCategory ctx,tryNonCategory ctx]
+  f' <- lift $ collectFirstM [tryCategory ctx,tryNonCategory ctx] <?? "In function call at " ++ formatFullContext c
   csAddRequired $ Set.fromList [sfType f']
   compileFunctionCall Nothing f' f
   where
@@ -907,11 +913,12 @@
   sameType <- csSameType t'
   s <- csCurrentScope
   let typeInstance = getType t' sameType s params
+  args <- getArgs es''
   -- TODO: This is unsafe if used in a type or category constructor.
   return (Positional [ValueType RequiredValue $ singleType $ JustTypeInstance t'],
-          UnwrappedSingle $ valueCreator (tiName t') ++ "(" ++ typeInstance ++ ", PassParamsArgs(" ++ es'' ++ "))")
+          UnwrappedSingle $ valueCreator (tiName t') ++ "(" ++ typeInstance ++ ", " ++ args ++ ")")
   where
-    getType _  True ValueScope _      = "parent"
+    getType _  True ValueScope _      = "PARAM_SELF"
     getType _  True TypeScope  _      = "PARAM_SELF"
     getType t2 _    _          params = typeCreator (tiName t2) ++ "(" ++ params ++ ")"
     -- Single expression, but possibly multi-return.
@@ -924,6 +931,14 @@
     checkArity (_,Positional [_]) = return ()
     checkArity (i,Positional ts)  =
       compilerErrorM $ "Initializer position " ++ show i ++ " has " ++ show (length ts) ++ " values but should have 1"
+    getArgs argEs = do
+      asNames <- lift $ collectArgNames $ pValues es
+      canForward <- case asNames of
+                         Just an -> csCanForward [] an
+                         _       -> return False
+      if canForward
+         then return "params_args"
+         else return $ "PassParamsArgs(" ++ argEs ++ ")"
 compileExpressionStart (UnambiguousLiteral l) = compileValueLiteral l
 
 compileValueLiteral :: (Ord c, Show c, CollectErrorsM m,
@@ -995,7 +1010,8 @@
   params <- expandParams2 ps2
   scope <- csCurrentScope
   scoped <- autoScope (sfScope f)
-  call <- assemble e scoped scope (sfScope f) params es''
+  paramsArgs <- getParamsArgs (pValues ps2) params es''
+  call <- assemble e scoped scope (sfScope f) paramsArgs
   return $ (ftReturns f'',OpaqueMulti call)
   where
     replaceSelfParam self (AssignedInstance c2 t) = do
@@ -1007,21 +1023,29 @@
       compilerBackgroundM $ "Parameter " ++ show n ++ " (from " ++ show (sfType f) ++ "." ++
         show (sfName f) ++ ") inferred as " ++ show t ++ " at " ++ formatFullContext c2
     backgroundMessage _ = return ()
-    joinParamsArgs ps2 es2 = "PassParamsArgs(" ++ intercalate ", " (ps2 ++ es2) ++ ")"
-    assemble Nothing _ ValueScope ValueScope ps2 es2 =
-      return $ callName (sfName f) ++ "(" ++ joinParamsArgs ps2 es2 ++ ")"
-    assemble Nothing _ TypeScope TypeScope ps2 es2 =
-      return $ callName (sfName f) ++ "(" ++ joinParamsArgs ps2 es2 ++ ")"
-    assemble Nothing scoped ValueScope TypeScope ps2 es2 =
-      return $ scoped ++ callName (sfName f) ++ "(" ++ joinParamsArgs ps2 es2 ++ ")"
-    assemble Nothing scoped _ _ ps2 es2 =
-      return $ scoped ++ callName (sfName f) ++ "(" ++ joinParamsArgs ps2 es2 ++ ")"
-    assemble (Just e2) _ _ ValueScope ps2 es2 =
-      return $ valueBase ++ "::Call(" ++ e2 ++ ", " ++ functionName f ++ ", " ++ joinParamsArgs ps2 es2 ++ ")"
-    assemble (Just e2) _ _ TypeScope ps2 es2 =
-      return $ typeBase ++ "::Call(" ++ e2 ++ ", " ++ functionName f ++ ", " ++ joinParamsArgs ps2 es2 ++ ")"
-    assemble (Just e2) _ _ _ ps2 es2 =
-      return $ e2 ++ ".Call(" ++ functionName f ++ ", " ++ joinParamsArgs ps2 es2 ++ ")"
+    getParamsArgs ps2 paramEs argEs = do
+      psNames <- lift $ collectParamNames ps2
+      asNames <- lift $ collectArgNames $ pValues es
+      canForward <- case (psNames,asNames) of
+                         (Just pn,Just an) -> csCanForward pn an
+                         _                 -> return False
+      if canForward
+         then return "params_args"
+         else return $ "PassParamsArgs(" ++ intercalate ", " (paramEs ++ argEs) ++ ")"
+    assemble Nothing _ ValueScope ValueScope paramsArgs =
+      return $ callName (sfName f) ++ "(" ++ paramsArgs ++ ")"
+    assemble Nothing _ TypeScope TypeScope paramsArgs =
+      return $ callName (sfName f) ++ "(" ++ paramsArgs ++ ")"
+    assemble Nothing scoped ValueScope TypeScope paramsArgs =
+      return $ scoped ++ callName (sfName f) ++ "(" ++ paramsArgs ++ ")"
+    assemble Nothing scoped _ _ paramsArgs =
+      return $ scoped ++ callName (sfName f) ++ "(" ++ paramsArgs ++ ")"
+    assemble (Just e2) _ _ ValueScope paramsArgs =
+      return $ valueBase ++ "::Call(" ++ e2 ++ ", " ++ functionName f ++ ", " ++ paramsArgs ++ ")"
+    assemble (Just e2) _ _ TypeScope paramsArgs =
+      return $ typeBase ++ "::Call(" ++ e2 ++ ", " ++ functionName f ++ ", " ++ paramsArgs ++ ")"
+    assemble (Just e2) _ _ _ paramsArgs =
+      return $ e2 ++ ".Call(" ++ functionName f ++ ", " ++ paramsArgs ++ ")"
     -- TODO: Lots of duplication with assignments and initialization.
     -- Single expression, but possibly multi-return.
     getValues [(Positional ts,e2)] = return (ts,[useAsArgs e2])
@@ -1035,7 +1059,16 @@
       compilerErrorM $ "Return position " ++ show i ++ " has " ++ show (length ts) ++ " values but should have 1"
     checkArg r fa t0 (i,t1) = do
       checkValueAssignment r fa t1 t0 <?? "In argument " ++ show i ++ " to " ++ show (sfName f)
+    collectParamNames = fmap sequence . mapCompilerM collectParamName
+    collectParamName = fmap getParamName . tryCompilerM . matchOnlyLeaf
+    getParamName (Just (JustParamName _ n)) = Just n
+    getParamName _ = Nothing
 
+collectArgNames :: CollectErrorsM m => [Expression c] -> m (Maybe [VariableName])
+collectArgNames = fmap sequence . mapCompilerM collectArgName where
+  collectArgName (Expression _ (NamedVariable (OutputValue _ n)) []) = return $ Just n
+  collectArgName _ = return Nothing
+
 guessParamsFromArgs :: (Ord c, Show c, CollectErrorsM m, TypeResolver r) =>
   r -> ParamFilters -> ScopedFunction c -> Positional (InstanceOrInferred c) ->
   Positional ValueType -> m (Positional GeneralInstance)
@@ -1171,19 +1204,28 @@
 
 expandGeneralInstance :: (CollectErrorsM m, CompilerContext c m s a) =>
   GeneralInstance -> CompilerState a m String
-expandGeneralInstance t
-  | t == minBound = return $ allGetter ++ "()"
-  | t == maxBound = return $ anyGetter ++ "()"
 expandGeneralInstance t = do
   r <- csResolver
   f <- csAllFilters
+  scope <- csCurrentScope
   t' <- lift $ dedupGeneralInstance r f t
-  reduceMergeTree getAny getAll getSingle t' where
+  t'' <-  case scope of
+               CategoryScope -> return t'
+               _ -> do
+                 self <- csSelfType
+                 lift $ reverseSelfInstance self t'
+  expand t'' where
+    expand t2
+      | t2 == minBound = return $ allGetter ++ "()"
+      | t2 == maxBound = return $ anyGetter ++ "()"
+      | otherwise = reduceMergeTree getAny getAll getSingle t2
     getAny ts = combine ts >>= return . (unionGetter ++)
     getAll ts = combine ts >>= return . (intersectGetter ++)
     getSingle (JustTypeInstance (TypeInstance t2 ps)) = do
-      ps' <- sequence $ map expandGeneralInstance $ pValues ps
-      return $ typeGetter t2 ++ "(T_get(" ++ intercalate "," ps' ++ "))"
+      ps' <- sequence $ map expand $ pValues ps
+      let count = length ps'
+      return $ typeGetter t2 ++ "(Params<" ++ show count ++ ">::Type(" ++ intercalate "," ps' ++ "))"
+    getSingle (JustParamName _ ParamSelf) = return "S<const TypeInstance>(PARAM_SELF)"
     getSingle (JustParamName _ p)  = do
       s <- csGetParamScope p
       scoped <- autoScope s
diff --git a/src/Module/ProcessMetadata.hs b/src/Module/ProcessMetadata.hs
--- a/src/Module/ProcessMetadata.hs
+++ b/src/Module/ProcessMetadata.hs
@@ -327,7 +327,8 @@
   let rs = Set.toList $ Set.fromList $ concat $ map getRequires os
   expectedFiles <- mapCompilerM (errorFromIO . canonicalizePath . (p2</>)) (ps++xs++ts)
   actualFiles   <- mapCompilerM (errorFromIO . canonicalizePath . (p2</>)) (ps2++xs2++ts2)
-  inputFiles    <- mapCompilerM (errorFromIO . canonicalizePath . (p2</>)) (xs++ts)
+  inputFiles    <- mapCompilerM (errorFromIO . canonicalizePath . (p2</>)) (ps++xs)
+  testFiles     <- mapCompilerM (errorFromIO . canonicalizePath . (p2</>)) ts
   collectAllM_ $ [
       checkHash,
       checkInput time (p </> moduleFilename),
@@ -335,6 +336,7 @@
     ] ++
     (map (checkDep time) $ is ++ is2) ++
     (map (checkInput time) inputFiles) ++
+    (map checkPresent testFiles) ++
     (map (checkInput time . getCachedPath d "") $ hxx ++ cxx) ++
     (map checkOutput bs) ++
     (map checkOutput ls) ++
@@ -344,6 +346,9 @@
     checkHash =
       when (not $ checkModuleVersionHash h m) $
         compilerErrorM $ "Module \"" ++ p ++ "\" was compiled with a different compiler setup"
+    checkPresent f = do
+      exists <- doesFileOrDirExist f
+      when (not exists) $ compilerErrorM $ "Required path \"" ++ f ++ "\" is missing"
     checkInput time f = do
       exists <- doesFileOrDirExist f
       when (not exists) $ compilerErrorM $ "Required path \"" ++ f ++ "\" is missing"
diff --git a/src/Parser/Common.hs b/src/Parser/Common.hs
--- a/src/Parser/Common.hs
+++ b/src/Parser/Common.hs
@@ -311,7 +311,7 @@
   compilerErrorM "#self is not allowed here"
 
 operatorSymbol :: TextParser Char
-operatorSymbol = labeled "operator symbol" $ satisfy (`Set.member` Set.fromList "+-*/%=!<>&|?")
+operatorSymbol = labeled "operator symbol" $ satisfy (`Set.member` Set.fromList "+-*/%=!<>&|")
 
 isKeyword :: TextParser ()
 isKeyword = foldr (<|>) empty $ map try [
diff --git a/src/Parser/Procedure.hs b/src/Parser/Procedure.hs
--- a/src/Parser/Procedure.hs
+++ b/src/Parser/Procedure.hs
@@ -405,7 +405,7 @@
     asInfix [e] [] <|> return e
     where
       -- NOTE: InitializeValue is parsed as ExpressionStart.
-      notInfix = literal <|> unary <|> expression
+      notInfix = literal <|> try unaryBuiltin <|> unary <|> expression
       asInfix es os = do
         c <- getSourceContext
         o <- infixOperator <|> functionOperator
@@ -428,6 +428,16 @@
       literal = do
         l <- sourceParser
         return $ Literal l
+      unaryBuiltin = do
+        c <- getSourceContext
+        infixFuncStart
+        n <- builtinUnary
+        ps <- try $ between (sepAfter $ string_ "<")
+                            (sepAfter $ string_ ">")
+                            (sepBy sourceParser (sepAfter $ string_ ",")) <|> return []
+        infixFuncEnd
+        e <- notInfix
+        return $ Expression [c] (BuiltinCall [c] $ FunctionCall [c] n (Positional ps) (Positional [e])) []
       unary = do
         c <- getSourceContext
         o <- unaryOperator <|> functionOperator
@@ -510,6 +520,14 @@
     kwTypename >> return BuiltinTypename
   ]
 
+builtinUnary :: TextParser FunctionName
+builtinUnary = foldr (<|>) empty $ map try [
+    kwPresent >> return BuiltinPresent,
+    kwReduce >> return BuiltinReduce,
+    kwRequire >> return BuiltinRequire,
+    kwStrong >> return BuiltinStrong
+  ]
+
 instance ParseFromSource (ExpressionStart SourceContext) where
   sourceParser = labeled "expression start" $
                  parens <|>
@@ -667,12 +685,9 @@
       return $ ValueCall [c] f
     conversion = labeled "type conversion" $ do
       c <- getSourceContext
-      valueSymbolGet
+      inferredParam
       t <- sourceParser -- NOTE: Should not need try here.
-      typeSymbolGet
-      n <- sourceParser
-      f <- parseFunctionCall c n
-      return $ ConvertedCall [c] t f
+      return $ TypeConversion [c] t
     selectReturn = labeled "return selection" $ do
       c <- getSourceContext
       sepAfter_ (string_ "{")
diff --git a/src/Parser/TypeInstance.hs b/src/Parser/TypeInstance.hs
--- a/src/Parser/TypeInstance.hs
+++ b/src/Parser/TypeInstance.hs
@@ -126,11 +126,11 @@
     requires = labeled "requires filter" $ do
       kwRequires
       t <- sourceParser
-      return $ TypeFilter FilterRequires $ singleType t
+      return $ TypeFilter FilterRequires t
     allows = labeled "allows filter" $ do
       kwAllows
       t <- sourceParser
-      return $ TypeFilter FilterAllows $ singleType t
+      return $ TypeFilter FilterAllows t
     defines = labeled "defines filter" $ do
       kwDefines
       t <- sourceParser
diff --git a/src/Test/Procedure.hs b/src/Test/Procedure.hs
--- a/src/Test/Procedure.hs
+++ b/src/Test/Procedure.hs
@@ -41,9 +41,12 @@
     checkShortParseFail "return var var",
     checkShortParseFail "return _ var",
     checkShortParseSuccess "return call()",
-    checkShortParseSuccess "return var.T<#x>.func()",
-    checkShortParseSuccess "return var, var.T<#x>.func()",
-    checkShortParseFail "return var  var.T<#x>.func()",
+    checkShortParseSuccess "return var?T<#x>.func()",
+    checkShortParseSuccess "return var?T<#x>",
+    checkShortParseSuccess "return var, var?T<#x>.func()",
+    checkShortParseFail "return var  var?T<#x>.func()",
+    checkShortParseFail "return var.T<#x>.func()",
+    checkShortParseFail "return var.T<#x>",
     checkShortParseFail "return var, _",
     checkShortParseFail "return T<#x> var",
     checkShortParseSuccess "return T<#x>{ val }",
@@ -60,15 +63,16 @@
 
     checkShortParseSuccess "\\ var",
     checkShortParseFail "\\ var var",
-    checkShortParseSuccess "\\ var.T<#x>.func().func2().func3()",
+    checkShortParseSuccess "\\ var?T<#x>.func().func2().func3()",
     checkShortParseSuccess "\\ T<#x>.func().func2().func3()",
     checkShortParseSuccess "\\ #x.func().func2().func3()",
-    checkShortParseFail "\\ var.T<#x>.T<#x>.func()",
-    checkShortParseFail "\\ var.T<#x>.T<#x>.func()",
-    checkShortParseFail "\\ var.T<#x>",
+    checkShortParseSuccess "\\ var?T<#x>?T<#x>.func()",
+    checkShortParseSuccess "\\ var?T<#x>",
+    checkShortParseSuccess "\\ var?#x",
+    checkShortParseSuccess "\\ var?[T1|T2]",
     checkShortParseFail "\\ T<#x> var",
-    checkShortParseSuccess "\\ T<#x>{ val, var.T<#x>.func() }",
-    checkShortParseFail "\\ T<#x>{ val var.T<#x>.func() }",
+    checkShortParseSuccess "\\ T<#x>{ val, var?T<#x>.func() }",
+    checkShortParseFail "\\ T<#x>{ val var?T<#x>.func() }",
     checkShortParseSuccess "\\ T<#x>{}.call()",
     checkShortParseFail "\\ T<#x>$call()",
     checkShortParseSuccess "\\ (T<#x>{}).call()",
@@ -122,28 +126,28 @@
     checkShortParseSuccess "while (var.func()) { \\ val.call() } update { \\ call() }",
 
     checkShortParseSuccess "scoped { T<#x> x <- y } in return _",
-    checkShortParseSuccess "scoped { T<#x> x <- y } in return var, var.T<#x>.func()",
-    checkShortParseSuccess "scoped { T<#x> x <- y } in \\ var.T<#x>.func()",
+    checkShortParseSuccess "scoped { T<#x> x <- y } in return var, var?T<#x>.func()",
+    checkShortParseSuccess "scoped { T<#x> x <- y } in \\ var?T<#x>.func()",
     checkShortParseSuccess "scoped { T<#x> x <- y } in _, weak T<#x> x <- var.func()",
 
     checkShortParseSuccess "scoped { T<#x> x <- y } in if (var.func()) { \\ val.call() }",
     checkShortParseSuccess "scoped { T<#x> x <- y } in while (var.func()) { \\ val.call() }",
 
-    checkShortParseSuccess "x <- (((var.func())).T.call())",
+    checkShortParseSuccess "x <- (((var.func()))?T.call())",
     checkShortParseSuccess "\\ (x <- var).func()",
     checkShortParseFail "x <- (((var.func()))",
     checkShortParseFail "(((x <- var.func())))",
     checkShortParseFail "(x) <- y",
     checkShortParseFail "T (x) <- y",
     checkShortParseFail "\\ (T x <- var).func()",
-    checkShortParseSuccess "\\ call(((var.func())).T.call())",
-    checkShortParseSuccess "if (((var.func()).T.call())) { }",
+    checkShortParseSuccess "\\ call(((var.func()))?T.call())",
+    checkShortParseSuccess "if (((var.func())?T.call())) { }",
     checkShortParseSuccess "fail(\"reason\")",
     checkShortParseFail "\\ fail(\"reason\")",
     checkShortParseSuccess "failed <- 10",
 
-    checkShortParseSuccess "\\var.T<#x>.func().func2().func3()",
-    checkShortParseSuccess "\\T<#x>{val,var.T<#x>.func()}",
+    checkShortParseSuccess "\\var?T<#x>.func().func2().func3()",
+    checkShortParseSuccess "\\T<#x>{val,var?T<#x>.func()}",
     checkShortParseSuccess "x<-var.func()",
     checkShortParseSuccess "T<#x>x<-var.func()",
     checkShortParseSuccess "_,weak T<#x>x<-var.func()",
@@ -151,7 +155,7 @@
     checkShortParseSuccess "if(v){\\c()}elif(v){\\c()}else{\\c()}",
     checkShortParseSuccess "if(v){\\c()}elif(v){\\c()}elif(v){\\c()}",
     checkShortParseSuccess "while(var.func()){\\val.call()}",
-    checkShortParseSuccess "scoped{T<#x>x<-y}in\\var.T<#x>.func()",
+    checkShortParseSuccess "scoped{T<#x>x<-y}in\\var?T<#x>.func()",
     checkShortParseSuccess "scoped{T<#x>x<-y}in{x<-1}",
     checkShortParseSuccess "scoped{T<#x>x<-y}in x<-1",
     checkShortParseFail "scoped{T<#x>x<-y}in{x}",
@@ -227,6 +231,12 @@
     checkShortParseSuccess "\\ call(){0}.bar()",
     checkShortParseFail "\\ call(){x}",
     checkShortParseFail "\\ call(){-1}",
+
+    checkShortParseSuccess "x <- `strong` y",
+    checkShortParseSuccess "x <- `present` y",
+    checkShortParseSuccess "x <- `require` y",
+    checkShortParseSuccess "x <- `reduce<#x,#y>` y",
+    checkShortParseFail "x <- `typename<#y>` y",
 
     checkParsesAs "'\"'"
                   (\e -> case e of
diff --git a/src/Test/testfiles/procedures.0rx b/src/Test/testfiles/procedures.0rx
--- a/src/Test/testfiles/procedures.0rx
+++ b/src/Test/testfiles/procedures.0rx
@@ -31,7 +31,7 @@
 
   \ z.call().call()
   x, weak [#k|Type] y, _ <- z.call()
-  x <- z.T<#z>.call().call()
+  x <- z?T<#z>.call().call()
   return _
   return a
   return a, z.call().call(), c
diff --git a/src/Types/DefinedCategory.hs b/src/Types/DefinedCategory.hs
--- a/src/Types/DefinedCategory.hs
+++ b/src/Types/DefinedCategory.hs
@@ -142,7 +142,9 @@
              "\n  ->\n" ++ show f ++ "\n---\n") ??> do
               f0' <- parsedToFunctionType f0
               f' <- parsedToFunctionType f
-              checkFunctionConvert r fm pm f0' f'
+              case s of
+                   CategoryScope -> checkFunctionConvert r Map.empty Map.empty f0' f'
+                   _             -> checkFunctionConvert r fm pm f0' f'
            return $ Map.insert n (ScopedFunction (c++c2) n t2 s as rs ps fs2 ([f0]++ms++ms2)) fa'
 
 pairProceduresToFunctions :: (Show c, CollectErrorsM m) =>
@@ -176,7 +178,7 @@
                      formatFullContextBrace (epContext p) ++
                      " does not correspond to a function"
     getPair (Just f) (Just p) = do
-      processPairs_ alwaysPair (sfArgs f) (avNames $ epArgs p) <!!
+      processPairs_ alwaysPair (fmap pvType $ sfArgs f) (fmap inputValueName $ avNames $ epArgs p) <!!
         ("Procedure for " ++ show (sfName f) ++
          formatFullContextBrace (avContext $ epArgs p) ++
          " has the wrong number of arguments" ++
@@ -184,7 +186,7 @@
       if isUnnamedReturns (epReturns p)
          then return ()
          else do
-           processPairs_ alwaysPair (sfReturns f) (nrNames $ epReturns p) <!!
+           processPairs_ alwaysPair (fmap pvType $ sfReturns f) (fmap ovName $ nrNames $ epReturns p) <!!
              ("Procedure for " ++ show (sfName f) ++
               formatFullContextBrace (nrContext $ epReturns p) ++
               " has the wrong number of returns" ++
diff --git a/src/Types/Procedure.hs b/src/Types/Procedure.hs
--- a/src/Types/Procedure.hs
+++ b/src/Types/Procedure.hs
@@ -51,6 +51,7 @@
   getOperatorContext,
   getOperatorName,
   getStatementContext,
+  inputValueName,
   isAssignableDiscard,
   isDiscardedInput,
   isFunctionOperator,
@@ -132,6 +133,10 @@
     iiContext :: [c]
   }
 
+inputValueName :: InputValue c -> VariableName
+inputValueName (DiscardInput _) = discardInputName
+inputValueName (InputValue _ n) = n
+
 isDiscardedInput :: InputValue c -> Bool
 isDiscardedInput (DiscardInput _) = True
 isDiscardedInput _                = False
@@ -330,7 +335,7 @@
 getValueLiteralContext (EmptyLiteral c)       = c
 
 data ValueOperation c =
-  ConvertedCall [c] TypeInstance (FunctionCall c) |
+  TypeConversion [c] GeneralInstance |
   ValueCall [c] (FunctionCall c) |
   SelectReturn [c] Int
   deriving (Show)
diff --git a/src/Types/TypeCategory.hs b/src/Types/TypeCategory.hs
--- a/src/Types/TypeCategory.hs
+++ b/src/Types/TypeCategory.hs
@@ -357,12 +357,13 @@
   }
 
 instance Show c => TypeResolver (CategoryResolver c) where
-    trRefines (CategoryResolver tm) (TypeInstance n1 ps1) n2
+    trRefines (CategoryResolver tm) ta@(TypeInstance n1 ps1) n2
       | n1 == n2 = do
         (_,t) <- getValueCategory tm ([],n1)
         processPairs_ alwaysPair (Positional $ map vpParam $ getCategoryParams t) ps1
         return ps1
       | otherwise = do
+        let self = singleType $ JustTypeInstance ta
         (_,t) <- getValueCategory tm ([],n1)
         let params = map vpParam $ getCategoryParams t
         assigned <- fmap Map.fromList $ processPairs alwaysPair (Positional params) ps1
@@ -370,8 +371,9 @@
         ps2 <- case n2 `Map.lookup` pa of
                     (Just x) -> return x
                     _ -> compilerErrorM $ "Category " ++ show n1 ++ " does not refine " ++ show n2
-        fmap Positional $ mapCompilerM (subAllParams assigned) $ pValues ps2
-    trDefines (CategoryResolver tm) (TypeInstance n1 ps1) n2 = do
+        fmap Positional $ mapCompilerM (subAllParams assigned >=> replaceSelfInstance self) $ pValues ps2
+    trDefines (CategoryResolver tm) ta@(TypeInstance n1 ps1) n2 = do
+      let self = singleType $ JustTypeInstance ta
       (_,t) <- getValueCategory tm ([],n1)
       let params = map vpParam $ getCategoryParams t
       assigned <- fmap Map.fromList $ processPairs alwaysPair (Positional params) ps1
@@ -379,7 +381,7 @@
       ps2 <- case n2 `Map.lookup` pa of
                   (Just x) -> return x
                   _ -> compilerErrorM $ "Category " ++ show n1 ++ " does not define " ++ show n2
-      fmap Positional $ mapCompilerM (subAllParams assigned) $ pValues ps2
+      fmap Positional $ mapCompilerM (subAllParams assigned >=> replaceSelfInstance self) $ pValues ps2
     trVariance (CategoryResolver tm) n = do
       (_,t) <- getCategory tm ([],n)
       return $ Positional $ map vpVariance $ getCategoryParams t
@@ -878,7 +880,7 @@
   let inheritByName  = fmap (nubBy sameFunction) $ Map.fromListWith (++) $ map (\f -> (sfName f,[f])) $ inheritValue ++ inheritType
   let explicitByName = Map.fromListWith (++) $ map (\f -> (sfName f,[f])) fs
   let allNames = Set.toList $ Set.union (Map.keysSet inheritByName) (Map.keysSet explicitByName)
-  mapCompilerM (mergeByName r fm inheritByName explicitByName) allNames where
+  mapCompilerM (mergeByName inheritByName explicitByName) allNames where
     getRefinesFuncs tm2 (ValueRefine c (TypeInstance n ts2)) = do
       (_,t) <- getValueCategory tm2 (c,n)
       let ps = map vpParam $ getCategoryParams t
@@ -893,27 +895,27 @@
       paired <- processPairs alwaysPair (Positional ps) ts2
       let assigned = Map.fromList $ (ParamSelf,selfType):paired
       mapCompilerM (unfixedSubFunction assigned) fs2
-    mergeByName r2 fm2 im em n =
-      tryMerge r2 fm2 n (n `Map.lookup` im) (n `Map.lookup` em)
+    mergeByName im em n =
+      tryMerge n (n `Map.lookup` im) (n `Map.lookup` em)
     -- Inherited without an override.
-    tryMerge _ _ n (Just is) Nothing
+    tryMerge n (Just is) Nothing
       | length is == 1 = return $ head is
       | otherwise = compilerErrorM $ "Function " ++ show n ++ " is inherited " ++
                                      show (length is) ++ " times:\n---\n" ++
                                      intercalate "\n---\n" (map show is)
     -- Not inherited.
-    tryMerge r2 fm2 n Nothing es = tryMerge r2 fm2 n (Just []) es
+    tryMerge n Nothing es = tryMerge n (Just []) es
     -- Explicit override, possibly inherited.
-    tryMerge r2 fm2 n (Just is) (Just es)
+    tryMerge n (Just is) (Just es)
       | length es /= 1 = compilerErrorM $ "Function " ++ show n ++ " is declared " ++
                                           show (length es) ++ " times:\n---\n" ++
                                           intercalate "\n---\n" (map show es)
       | otherwise = do
         let ff@(ScopedFunction c n2 t s as rs2 ps fa ms) = head es
-        mapCompilerM_ (checkMerge r2 fm2 ff) is
+        mapCompilerM_ (checkMerge ff) is
         return $ ScopedFunction c n2 t s as rs2 ps fa (ms ++ is)
         where
-          checkMerge r3 fm3 f1 f2
+          checkMerge f1 f2
             | sfScope f1 /= sfScope f2 =
               compilerErrorM $ "Cannot merge " ++ show (sfScope f2) ++ " with " ++
                                show (sfScope f1) ++ " in function merge:\n---\n" ++
@@ -922,7 +924,9 @@
               "In function merge:\n---\n" ++ show f2 ++ "\n  ->\n" ++ show f1 ++ "\n---\n" ??> do
                 f1' <- parsedToFunctionType f1
                 f2' <- parsedToFunctionType f2
-                checkFunctionConvert r3 fm3 pm f2' f1'
+                case sfScope f1 of
+                     CategoryScope -> checkFunctionConvert r Map.empty Map.empty f2' f1'
+                     _             -> checkFunctionConvert r fm pm f2' f1'
 
 data FunctionName =
   FunctionName {
diff --git a/src/Types/TypeInstance.hs b/src/Types/TypeInstance.hs
--- a/src/Types/TypeInstance.hs
+++ b/src/Types/TypeInstance.hs
@@ -62,6 +62,7 @@
   replaceSelfValueType,
   requiredParam,
   requiredSingleton,
+  reverseSelfInstance,
   selfType,
   uncheckedSubFilter,
   uncheckedSubFilters,
@@ -724,6 +725,16 @@
       fs' <- mapCompilerM (uncheckedSubFilter replace) fs
       return (n,fs')
 
+reverseSelfInstance :: CollectErrorsM m =>
+  TypeInstance -> GeneralInstance -> m GeneralInstance
+reverseSelfInstance self = reduceMergeTree subAny subAll subSingle where
+  -- NOTE: Don't use mergeAnyM because it will fail if the union is empty.
+  subAny = fmap mergeAny . sequence
+  subAll = fmap mergeAll . sequence
+  -- NOTE: Equality should be fine here, since self should only have param names.
+  subSingle (JustTypeInstance t) | t == self = return $ singleType $ JustParamName True ParamSelf
+  subSingle t = return $ singleType t
+
 replaceSelfValueType :: CollectErrorsM m =>
   GeneralInstance -> ValueType -> m ValueType
 replaceSelfValueType self (ValueType s t) = do
@@ -738,7 +749,7 @@
   subAll = fmap mergeAll . sequence
   subSingle (JustParamName _ ParamSelf) = return self
   subSingle (JustTypeInstance t)        = fmap (singleType . JustTypeInstance) $ replaceSelfSingle self t
-  subSingle p                           = return $ singleType p
+  subSingle t                           = return $ singleType t
 
 replaceSelfSingle :: CollectErrorsM m =>
   GeneralInstance -> TypeInstance -> m TypeInstance
diff --git a/tests/builtin-types.0rt b/tests/builtin-types.0rt
--- a/tests/builtin-types.0rt
+++ b/tests/builtin-types.0rt
@@ -321,14 +321,48 @@
   \ Testing.checkEquals(String.default(),"")
 }
 
-unittest stringLessThan {
-  if (!("x" `String.lessThan` "y")) {
-    fail("Failed")
-  }
+unittest boolDuplicate {
+  \ Testing.checkTrue(true.duplicate())
 }
 
-unittest boolEquals {
-  if (!(true `Bool.equals` true)) {
+unittest charDuplicate {
+  \ Testing.checkEquals('a'.duplicate(),'a')
+}
+
+unittest floatDuplicate {
+  \ Testing.checkEquals((1.1).duplicate(),1.1)
+}
+
+unittest intDuplicate {
+  \ Testing.checkEquals((123).duplicate(),123)
+}
+
+unittest stringDuplicate {
+  \ Testing.checkEquals("string".duplicate(),"string")
+}
+
+unittest boolHashed {
+  \ Testing.checkNotEquals(true.hashed(),false.hashed())
+}
+
+unittest charHashed {
+  \ Testing.checkNotEquals('a'.hashed(),'b'.hashed())
+}
+
+unittest floatHashed {
+  \ Testing.checkNotEquals((1.1).hashed(),(1.2).hashed())
+}
+
+unittest intHashed {
+  \ Testing.checkNotEquals((123).hashed(),(124).hashed())
+}
+
+unittest stringHashed {
+  \ Testing.checkNotEquals("string12".hashed(),"string21".hashed())
+}
+
+unittest stringLessThan {
+  if (!("x" `String.lessThan` "y")) {
     fail("Failed")
   }
 }
@@ -428,6 +462,27 @@
   String s <- (1.1).formatted()
   if (s != "1.1") {  // precision might vary
     fail(s)
+  }
+}
+
+unittest boolEquals {
+  if (!(true `Bool.equals` true)) {
+    fail("Failed")
+  }
+}
+
+unittest boolLessThan {
+  if (!(false `Bool.lessThan` true)) {
+    fail("Failed")
+  }
+  if (true `Bool.lessThan` true) {
+    fail("Failed")
+  }
+  if (true `Bool.lessThan` false) {
+    fail("Failed")
+  }
+  if (false `Bool.lessThan` false) {
+    fail("Failed")
   }
 }
 
diff --git a/tests/cli-tests.sh b/tests/cli-tests.sh
--- a/tests/cli-tests.sh
+++ b/tests/cli-tests.sh
@@ -147,31 +147,38 @@
   local output=$(do_zeolite -p "$ZEOLITE_PATH" -t tests/freshness || true)
   require_patterns "$output" <<END
 tests/freshness[^/].+out of date
+freshness/private2\.0rp.+missing
+freshness/public1\.0rp.+newer
+freshness/public2\.0rp.+missing
 freshness/public3\.0rp.+not present
 freshness/source1\.0rx.+newer
 freshness/source2\.0rx.+missing
 freshness/source3\.0rx.+not present
+freshness/sub1/private2\.0rp.+missing
+freshness/sub1/public1\.0rp.+newer
+freshness/sub1/public2\.0rp.+missing
 freshness/sub1/public3\.0rp.+not present
 freshness/sub1/source1\.0rx.+newer
 freshness/sub1/source2\.0rx.+missing
 freshness/sub1/source3\.0rx.+not present
-freshness/sub1/test1\.0rt.+newer
 freshness/sub1/test2\.0rt.+missing
 freshness/sub1/test3\.0rt.+not present
+freshness/sub2/private2\.0rp.+missing
+freshness/sub2/public1\.0rp.+newer
+freshness/sub2/public2\.0rp.+missing
 freshness/sub2/public3\.0rp.+not present
 freshness/sub2/source1\.0rx.+newer
 freshness/sub2/source2\.0rx.+missing
 freshness/sub2/source3\.0rx.+not present
-freshness/sub2/test1\.0rt.+newer
 freshness/sub2/test2\.0rt.+missing
 freshness/sub2/test3\.0rt.+not present
-freshness/test1\.0rt.+newer
 freshness/test2\.0rt.+missing
 freshness/test3\.0rt.+not present
 freshness/.zeolite-cache/public_[a-f0-9]+\.so.+missing
 END
 [[ $? -eq 0 ]] || return 1
   exclude_patterns "$output" <<END
+\.0rt.+newer
 cpp
 hpp
 \.o\b
diff --git a/tests/conversions.0rt b/tests/conversions.0rt
new file mode 100644
--- /dev/null
+++ b/tests/conversions.0rt
@@ -0,0 +1,144 @@
+/* -----------------------------------------------------------------------------
+Copyright 2020-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 "type conversions" {
+  success
+}
+
+unittest toType {
+  \ Testing.checkEquals(Helper.getType(Value.create()?Base1),"Base1")
+}
+
+unittest toAny {
+  \ Testing.checkEquals(Helper.getType(Value.create()?any),"any")
+}
+
+unittest toUnion {
+  \ Testing.checkEquals(Helper.getType(Value.create()?[Base1|Base2]),"[Base1|Base2]")
+}
+
+unittest toIntersect {
+  \ Testing.checkEquals(Helper.getType(Value.create()?[Base1&Base2]),"[Base1&Base2]")
+}
+
+unittest toParam {
+  \ Testing.checkEquals(Helper.toParam<Value,Base1>(Value.create()),"Base1")
+}
+
+unittest toTypeOptional {
+  optional Value value <- Value.create()
+  \ Testing.checkEquals(Helper.getType2(value?Base1),"Base1")
+}
+
+unittest toAnyOptional {
+  optional Value value <- Value.create()
+  \ Testing.checkEquals(Helper.getType2(value?any),"any")
+}
+
+unittest convertedCall {
+  Value value <- Value.create()
+  \ value?Base1.call()
+}
+
+concrete Helper {
+  @type toParam<#x,#y>
+    #x requires #y
+  (#x) -> (String)
+
+  @type getType<#x> (#x) -> (String)
+  @type getType2<#x> (optional #x) -> (String)
+}
+
+define Helper {
+  toParam (x) {
+    return getType(x?#y)
+  }
+
+  getType (_) {
+    return typename<#x>().formatted()
+  }
+
+  getType2 (_) {
+    return typename<#x>().formatted()
+  }
+}
+
+@value interface Base2 {
+  call () -> ()
+}
+
+@value interface Base1 {
+  call () -> ()
+}
+
+concrete Value {
+  refines Base1
+  refines Base2
+
+  @type create () -> (Value)
+  @value call () -> ()
+}
+
+define Value {
+  call () {}
+
+  create () {
+    return Value{}
+  }
+}
+
+
+testcase "conversion bad type" {
+  error
+  require "Base"
+  require "Value"
+}
+
+unittest test {
+  Value value <- Value.create()
+  \ value?Base
+}
+
+@value interface Base {
+  call () -> ()
+}
+
+concrete Value {
+  @value call () -> ()
+  @type create () -> (Value)
+}
+
+define Value {
+  call () {}
+
+  create () {
+    return Value{}
+  }
+}
+
+
+testcase "conversion of optional preserves optional" {
+  error
+  require "require"
+  require "optional Formatted"
+}
+
+unittest test {
+  optional Int value <- empty
+  \ value?Formatted.formatted()
+}
diff --git a/tests/defer.0rt b/tests/defer.0rt
--- a/tests/defer.0rt
+++ b/tests/defer.0rt
@@ -86,11 +86,10 @@
 }
 
 
-testcase "cannot defer @value member" {
+testcase "can defer @value member" {
   error
-  require "defer"
   require "value"
-  require "local"
+  require "initialized"
 }
 
 concrete Type {}
@@ -101,15 +100,15 @@
   @value call () -> ()
   call () {
     value <- defer
+    \ value
   }
 }
 
 
-testcase "cannot defer @category member" {
+testcase "can defer @category member" {
   error
-  require "defer"
   require "value"
-  require "local"
+  require "initialized"
 }
 
 concrete Type {}
@@ -120,6 +119,7 @@
   @value call () -> ()
   call () {
     value <- defer
+    \ value
   }
 }
 
@@ -286,6 +286,22 @@
 unittest test {
   scoped {
   } in Int value <- defer
+}
+
+
+testcase "deferred propagated from scoped statement" {
+  compiles
+}
+
+unittest test {
+  Int value <- defer
+  scoped {
+  } in if (true) {
+    value <- 1
+  } else {
+    value <- 2
+  }
+  \ value
 }
 
 
diff --git a/tests/fast-static/program.0rx b/tests/fast-static/program.0rx
--- a/tests/fast-static/program.0rx
+++ b/tests/fast-static/program.0rx
@@ -4,9 +4,9 @@
 
 define Program {
   run () {
-    \ LazyStream<Formatted>.new()
-        .append($ExprLookup[MODULE_PATH]$ + "\n")
-        .append("Static linking works!\n")
-        .writeTo(SimpleOutput.stdout())
+    \ BasicOutput.stdout()
+        .write($ExprLookup[MODULE_PATH]$ + "\n")
+        .write("Static linking works!\n")
+        .flush()
   }
 }
diff --git a/tests/filters.0rt b/tests/filters.0rt
--- a/tests/filters.0rt
+++ b/tests/filters.0rt
@@ -364,3 +364,155 @@
     \ call<Char>(empty)
   }
 }
+
+
+testcase "filters with internal functions" {
+  compiles
+}
+
+concrete Test {}
+
+define Test {
+  @category call1<#x,#y>
+    #y immutable
+    #x defines LessThan<#x>
+    #x requires Formatted
+    #y allows Int
+  (#x,#y) -> ()
+  call1 (x,y) {
+    \ call1<#x,#y>(x,y)
+  }
+
+  @type call2<#x,#y>
+    #y immutable
+    #x defines LessThan<#x>
+    #x requires Formatted
+    #y allows Int
+  (#x,#y) -> ()
+  call2 (x,y) {
+    \ call2<#x,#y>(x,y)
+    \ call1<#x,#y>(x,y)
+  }
+
+  @value call3<#x,#y>
+    #y immutable
+    #x defines LessThan<#x>
+    #x requires Formatted
+    #y allows Int
+  (#x,#y) -> ()
+  call3 (x,y) {
+    \ call3<#x,#y>(x,y)
+    \ call2<#x,#y>(x,y)
+    \ call1<#x,#y>(x,y)
+  }
+}
+
+
+testcase "filters on internal @category function with same param names as category" {
+  compiles
+}
+
+concrete Test<#x,#y> {}
+
+define Test {
+  @category call<#x,#y>
+    #y immutable
+    #x defines LessThan<#x>
+    #x requires Formatted
+    #y allows Int
+  (#x,#y) -> ()
+  call (x,y) {
+    \ call<#x,#y>(x,y)
+  }
+}
+
+
+testcase "allows intersection success" {
+  compiles
+}
+
+unittest test {
+  \ Test.call<AsBool>()
+}
+
+concrete Test {
+  @type call<#x>
+    #x allows [Formatted&AsBool]
+  () -> (#x)
+}
+
+define Test {
+  call () {
+    return "message"
+  }
+}
+
+
+testcase "allows intersection fail" {
+  error
+  require "Int"
+  require "Formatted"
+  require "AsBool"
+  exclude "String"
+}
+
+unittest test {
+  \ Test.call<Int>()
+}
+
+concrete Test {
+  @type call<#x>
+    #x allows [Formatted&AsBool]
+  () -> (#x)
+}
+
+define Test {
+  call () {
+    return "message"
+  }
+}
+
+
+testcase "requires union success" {
+  compiles
+}
+
+unittest test {
+  \ Test.call<Int>(1)
+}
+
+concrete Test {
+  @type call<#x>
+    #x requires [Int|String]
+  (#x) -> ()
+}
+
+define Test {
+  call (x) {
+    \ x?Formatted.formatted()
+  }
+}
+
+
+testcase "requires union fail" {
+  error
+  require "Formatted"
+  require "Int"
+  require "String"
+}
+
+unittest test {
+  \ Test.call<Formatted>(1)
+}
+
+concrete Test {
+  @type call<#x>
+    #x requires [Int|String]
+  (#x) -> ()
+}
+
+define Test {
+  call (x) {
+    \ x?Formatted.formatted()
+  }
+}
diff --git a/tests/function-calls.0rt b/tests/function-calls.0rt
--- a/tests/function-calls.0rt
+++ b/tests/function-calls.0rt
@@ -16,157 +16,269 @@
 
 // Author: Kevin P. Barry [ta0kira@gmail.com]
 
-testcase "converted call" {
-  success
+testcase "call from union failed merge" {
+  error
+  require "\[Value1\|Value2\]"
+  require "get"
+  require "[Mm]ultiple"
 }
 
 unittest test {
-  Value value <- Value.create()
-  \ value.Base.call()
+  [Value1|Value2] value <- Value1.create()
+  \ value.get()
 }
 
-@value interface Base {
-  call () -> ()
+@value interface Base<|#x> {
+  get () -> (#x)
 }
 
-concrete Value {
-  refines Base
+concrete Value1 {
+  refines Base<Int>
+  @type create () -> (Value1)
+}
 
-  @type create () -> (Value)
+define Value1 {
+  get () {
+    return 1
+  }
+
+  create () {
+    return Value1{ }
+  }
 }
 
-define Value {
-  call () {}
+concrete Value2 {
+  refines Base<String>
+  @type create () -> (Value2)
+}
 
+define Value2 {
+  get () {
+    return "message"
+  }
+
   create () {
-    return Value{}
+    return Value2{ }
   }
 }
 
 
-testcase "converted call bad type" {
+testcase "call from union ambiguous base" {
   error
+  require "\[Value1\|Value2\]"
+  require "get"
+  require "explicit"
   require "Base"
 }
 
 unittest test {
-  Value value <- Value.create()
-  \ value.Base.call()
+  [Value1|Value2] value <- Value1.create()
+  \ value.get()
 }
 
-@value interface Base {
-  call () -> ()
+@value interface Base<|#x> {
+  get () -> (#x)
 }
 
-concrete Value {
-  @value call () -> ()
-  @type create () -> (Value)
+concrete Value1 {
+  refines Base<Int>
+  @type create () -> (Value1)
+  @value get () -> (Int)
 }
 
-define Value {
-  call () {}
+define Value1 {
+  get () {
+    return 1
+  }
 
   create () {
-    return Value{}
+    return Value1{ }
   }
 }
 
+concrete Value2 {
+  refines Base<String>
+  @type create () -> (Value2)
+}
 
-testcase "call from union" {
+define Value2 {
+  get () {
+    return "message"
+  }
+
+  create () {
+    return Value2{ }
+  }
+}
+
+
+testcase "call from union bad function name" {
   error
-  require "\[Base\|Value\]"
+  require "\[Int\|String\]"
+  require "asFloat"
 }
 
 unittest test {
-  [Base|Value] value <- Value.create()
-  \ value.call()
+  [Int|String] value <- 1
+  \ value.asFloat()
 }
 
-@value interface Base {
-  call () -> ()
+
+testcase "call from union automatic merge" {
+  success
 }
 
-concrete Value {
-  refines Base
+unittest test {
+  [Value1|Value2] value <- Value1.create()
+  \ Testing.checkEquals(value.get().formatted(),"1")
+}
 
-  @type create () -> (Value)
+@value interface Base<|#x> {
+  get () -> (#x)
 }
 
-define Value {
-  call () {}
+concrete Value1 {
+  refines Base<Formatted>
+  @type create () -> (Value1)
+}
 
+define Value1 {
+  get () {
+    return 1
+  }
+
   create () {
-    return Value{}
+    return Value1{ }
   }
 }
 
+concrete Value2 {
+  refines Base<String>
+  @type create () -> (Value2)
+}
 
-testcase "call from union with conversion" {
-  success
+define Value2 {
+  get () {
+    return "message"
+  }
+
+  create () {
+    return Value2{ }
+  }
 }
 
+
+testcase "call from union automatic merge correct direction" {
+  error
+  require "Formatted.+Int"
+  require "\+"
+  exclude "\[Value1\|Value2\]"
+  exclude "get"
+}
+
 unittest test {
-  [Base|Value] value <- Value.create()
-  \ value.Base.call()
+  [Value1|Value2] value <- Value1.create()
+  \ value.get()+1
 }
 
-@value interface Base {
-  call () -> ()
+@value interface Base<|#x> {
+  get () -> (#x)
 }
 
-concrete Value {
-  refines Base
+concrete Value1 {
+  refines Base<Int>
+  @type create () -> (Value1)
+}
 
-  @type create () -> (Value)
+define Value1 {
+  get () {
+    return 1
+  }
+
+  create () {
+    return Value1{ }
+  }
 }
 
-define Value {
-  call () {}
+concrete Value2 {
+  refines Base<Formatted>
+  @type create () -> (Value2)
+}
 
+define Value2 {
+  get () {
+    return "message"
+  }
+
   create () {
-    return Value{}
+    return Value2{ }
   }
 }
 
 
-testcase "call from intersect" {
+testcase "call from union explicit conversion" {
   success
 }
 
 unittest test {
-  [Base1&Base2] value <- Value.create()
-  \ value.call()
+  [Value1|Value2] value <- Value1.create()
+  \ Testing.checkEquals(value?Base<Formatted>.get().formatted(),"1")
 }
 
-@value interface Base1 {
-  call () -> ()
+@value interface Base<|#x> {
+  get () -> (#x)
 }
 
-@value interface Base2 {}
+concrete Value1 {
+  refines Base<Int>
+  @type create () -> (Value1)
+}
 
-concrete Value {
-  refines Base1
-  refines Base2
+define Value1 {
+  get () {
+    return 1
+  }
 
-  @type create () -> (Value)
+  create () {
+    return Value1{ }
+  }
 }
 
-define Value {
-  call () {}
+concrete Value2 {
+  refines Base<String>
+  @type create () -> (Value2)
+}
 
+define Value2 {
+  get () {
+    return "message"
+  }
+
   create () {
-    return Value{}
+    return Value2{ }
   }
 }
 
 
-testcase "call from intersect with conversion" {
+testcase "call from all fails" {
+  error
+  require "formatted"
+  require "all"
+}
+
+unittest test {
+  optional all value <- empty
+  \ require(value).formatted()
+}
+
+
+testcase "call from intersect" {
   success
 }
 
 unittest test {
   [Base1&Base2] value <- Value.create()
-  \ value.Base1.call()
+  \ value.call()
 }
 
 @value interface Base1 {
@@ -197,7 +309,7 @@
 
 unittest test {
   [Base1&Base2] value <- Value.create()
-  \ value.Base1.call()
+  \ value?Base1.call()
 }
 
 @value interface Base1 {
@@ -734,5 +846,61 @@
 
   call (_,x,_,y) {
     return x+y.formatted()
+  }
+}
+
+
+testcase "@type function calls" {
+  success
+}
+
+unittest sameType {
+  \ Testing.checkEquals(Value<Int>.sameType(),"Value<Int>")
+}
+
+unittest sameValue {
+  \ Testing.checkEquals(Value<Int>.create().sameValue(),"Value<Int>")
+}
+
+unittest diffType {
+  \ Testing.checkEquals(Value<Int>.diffType<String>(),"Value<String>")
+}
+
+unittest diffValue {
+  \ Testing.checkEquals(Value<Int>.create().diffValue<String>(),"Value<String>")
+}
+
+concrete Value<#x> {
+  @type create () -> (#self)
+  @type call () -> (String)
+  @type  sameType      () -> (String)
+  @value sameValue     () -> (String)
+  @type  diffType<#y>  () -> (String)
+  @value diffValue<#y> () -> (String)
+}
+
+define Value {
+  create () {
+    return #self{ }
+  }
+
+  call () {
+    return typename<#self>().formatted()
+  }
+
+  sameType () {
+    return Value<#x>.call()
+  }
+
+  sameValue () {
+    return Value<#x>.call()
+  }
+
+  diffType () {
+    return Value<#y>.call()
+  }
+
+  diffValue () {
+    return Value<#y>.call()
   }
 }
diff --git a/tests/leak-check/leak-check.0rx b/tests/leak-check/leak-check.0rx
--- a/tests/leak-check/leak-check.0rx
+++ b/tests/leak-check/leak-check.0rx
@@ -10,9 +10,7 @@
       \ message()
     } elif (Argv.global().readAt(1) == "race") {
       \ runWith(1000,0)
-      \ LazyStream<Formatted>.new()
-          .append("no race conditions this time\n")
-          .writeTo(SimpleOutput.stderr())
+      \ BasicOutput.stderr().writeNow("no race conditions this time\n")
     } elif (Argv.global().readAt(1) == "leak") {
       \ runWith(100,0)
     } elif (Argv.global().readAt(1) == "forever") {
@@ -41,19 +39,19 @@
     #f defines ThreadFactory
   (Int,Int,ReadAt<BarrierWait>) -> ()
   doIteration (i,size,barriers) {
-    \ LazyStream<Formatted>.new()
-        .append(typename<#f>())
-        .append(": iteration ")
-        .append(i)
-        .append("\n")
-        .writeTo(SimpleOutput.stderr())
+    \ BasicOutput.stderr()
+        .write(typename<#f>())
+        .write(": iteration ")
+        .write(i)
+        .write("\n")
+        .flush()
 
     optional LeakTest original <- LeakTest{ Vector:createSize<Int>(size) }
     weak LeakTest checkedValue <- original
     $ReadOnly[checkedValue]$
 
     scoped {
-      Vector<Thread> threads <- Vector:create<Thread>()
+      Vector<Thread> threads <- Vector<Thread>.new()
     } in {
       traverse (Counter.zeroIndexed(barriers.size()-1) -> Int j) {
         \ threads.push(ProcessThread.from(#f.create(original,barriers.readAt(j+1))).start())
diff --git a/tests/self-type.0rt b/tests/self-type.0rt
--- a/tests/self-type.0rt
+++ b/tests/self-type.0rt
@@ -584,3 +584,175 @@
     #x requires #self
   () -> ()
 }
+
+
+testcase "convert self to interface with #self as param" {
+  compiles
+}
+
+@value interface Reader<|#x> {
+  call () -> (Reader<#x>)
+}
+
+@type interface Factory<|#x> {
+  create () -> (Reader<#x>)
+}
+
+concrete Type {
+  defines Factory<#self>
+  refines Reader<#self>
+}
+
+define Type {
+  call () (y) {
+    y <- self
+    return self
+  }
+
+  create () (y) {
+    y <- Type{ }
+    return Type{ }
+  }
+
+  @category something () -> (Reader<Type>)
+  something () (y) {
+    y <- Type{ }
+    return Type{ }
+  }
+}
+
+
+testcase "bad implicit conversion with #self in param" {
+  error
+  require "Int"
+  require "Type"
+  require "Reader"
+  exclude "#self"
+}
+
+@value interface Reader<|#x> {
+  call () -> (Reader<#x>)
+}
+
+concrete Type {
+  refines Reader<#self>
+}
+
+define Type {
+  call () (y) {
+    optional Reader<Int> value <- empty
+    y <- require(value)
+  }
+}
+
+
+testcase "bad explicit conversion with #self in param" {
+  error
+  require "Int"
+  require "Type"
+  require "Reader"
+  exclude "#self"
+}
+
+@value interface Reader<|#x> {}
+
+concrete Type {
+  refines Reader<#self>
+}
+
+define Type {
+  @value call () -> ()
+  call () {
+    \ self?Reader<Int>
+  }
+}
+
+
+testcase "#self cannot be contravariant in refine" {
+  error
+  require "#self"
+  require "Base"
+  require "contravariant"
+}
+
+@value interface Base<#x|> {}
+
+concrete Type {
+  refines Base<#self>
+}
+
+define Type {}
+
+
+testcase "#self cannot be contravariant in define" {
+  error
+  require "#self"
+  require "Base"
+  require "contravariant"
+}
+
+@type interface Base<#x|> {}
+
+concrete Type {
+  defines Base<#self>
+}
+
+define Type {}
+
+
+testcase "#self cannot be invariant in refine" {
+  error
+  require "#self"
+  require "Base"
+  require "invariant"
+}
+
+@value interface Base<#x> {}
+
+concrete Type {
+  refines Base<#self>
+}
+
+define Type {}
+
+
+testcase "#self cannot be invariant in define" {
+  error
+  require "#self"
+  require "Base"
+  require "invariant"
+}
+
+@type interface Base<#x> {}
+
+concrete Type {
+  defines Base<#self>
+}
+
+define Type {}
+
+
+testcase "#self nested in value init with filters" {
+  success
+}
+
+unittest test {
+  \ Testing.checkEquals(Type<Int>.create().formatted(),"Type<Type<Int>>")
+}
+
+concrete Type<|#x> {
+  refines Formatted
+  #x requires Formatted
+
+  @type create () -> (Type<#self>)
+}
+
+define Type {
+  create () {
+    return Type<#self>{ }
+  }
+
+  formatted () {
+    return typename<#self>().formatted()
+  }
+}
diff --git a/tests/simulate-refs/main.0rx b/tests/simulate-refs/main.0rx
--- a/tests/simulate-refs/main.0rx
+++ b/tests/simulate-refs/main.0rx
@@ -22,7 +22,7 @@
 
 define SimulateRefs {
   @category Int errorLimit <- 10
-  @category RandomUniform random <- RandomUniform.new(0.0,1.0).setSeed(Realtime.monoSeconds().asInt())
+  @category RandomUniform random <- RandomUniform.probability().setSeed(Realtime.monoSeconds().asInt())
 
   run () {
     Bool useBroken <- Argv.global().size() > 1 && Argv.global().readAt(1) == "broken"
@@ -32,11 +32,11 @@
     }
 
     traverse (Counter.unlimited() -> Int i) {
-      \ LazyStream<Formatted>.new()
-          .append("Iteration ")
-          .append(i)
-          .append("\n")
-          .writeTo(SimpleOutput.stderr())
+      \ BasicOutput.stderr()
+          .write("Iteration ")
+          .write(i)
+          .write("\n")
+          .flush()
 
       if (!runOnce(useBroken) && (errorLimit <- errorLimit-1) == 0) {
         break
@@ -46,7 +46,7 @@
 
   @type getRoutines (ReferenceState) -> (DefaultOrder<StateMachine>)
   getRoutines (state) {
-    return Vector:create<StateMachine>()
+    return Vector<StateMachine>.new()
         .append(Routines.newShared("shared1",state))
         .append(Routines.newWeak("weak1",state))
         .append(Routines.newWeak("weak2",state))
@@ -54,7 +54,7 @@
 
   @type getRoutinesBroken (ReferenceState) -> (DefaultOrder<StateMachine>)
   getRoutinesBroken (state) {
-    return Vector:create<StateMachine>()
+    return Vector<StateMachine>.new()
         .append(Routines.newSharedBroken("shared1",state))
         .append(Routines.newWeakBroken("weak1",state))
         .append(Routines.newWeakBroken("weak2",state))
@@ -80,19 +80,19 @@
         error <- "expected unlocked final reference"
       }
     } in if (present(error)) {
-      \ LazyStream<Formatted>.new()
-          .append("Error in final state: ")
-          .append(require(error))
-          .append("\nFinal state: ")
-          .append(state)
-          .append("\n")
-          .writeTo(SimpleOutput.stdout())
+      \ BasicOutput.stdout()
+          .write("Error in final state: ")
+          .write(require(error))
+          .write("\nFinal state: ")
+          .write(state)
+          .write("\n")
+          .flush()
       traverse (state.getOperations() -> Formatted operation) {
-        \ LazyStream<Formatted>.new()
-            .append("  ")
-            .append(operation)
-            .append("\n")
-            .writeTo(SimpleOutput.stdout())
+        \ BasicOutput.stdout()
+            .write("  ")
+            .write(operation)
+            .write("\n")
+            .flush()
       }
       return false
     } else {
diff --git a/tests/simulate-refs/ref-state.0rx b/tests/simulate-refs/ref-state.0rx
--- a/tests/simulate-refs/ref-state.0rx
+++ b/tests/simulate-refs/ref-state.0rx
@@ -25,7 +25,7 @@
   @value [Append<Formatted>&DefaultOrder<Formatted>] operations
 
   new () {
-    return ReferenceState{ 0, 0, false, 0, 0, Vector:create<Formatted>() }
+    return ReferenceState{ 0, 0, false, 0, 0, Vector<Formatted>.new() }
   }
 
   addOperation (name) {
diff --git a/tests/simulate-refs/state.0rx b/tests/simulate-refs/state.0rx
--- a/tests/simulate-refs/state.0rx
+++ b/tests/simulate-refs/state.0rx
@@ -18,7 +18,7 @@
 
 define StateExecutor {
   multiplexStates (original,random) {
-    SearchTree<Int,StateMachine> states <- SearchTree<Int,StateMachine>.new()
+    HashedMap<Int,StateMachine> states <- HashedMap<Int,StateMachine>.new()
     CategoricalTree<Int> weights <- CategoricalTree<Int>.new()
 
     scoped {
diff --git a/tests/unary-functions.0rt b/tests/unary-functions.0rt
--- a/tests/unary-functions.0rt
+++ b/tests/unary-functions.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2020 Kevin P. Barry
+Copyright 2020-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.
@@ -138,4 +138,33 @@
   neg (x) {
     return -x
   }
+}
+
+
+testcase "builtins as unary" {
+  success
+}
+
+unittest requireUnary {
+  optional Int value <- 1
+  \ Testing.checkEquals(`require` value,1)
+}
+
+unittest presentUnary {
+  optional Int value <- 1
+  \ Testing.checkTrue(`present` value)
+  value <- empty
+  \ Testing.checkTrue(! `present` value)
+}
+
+unittest strongUnary {
+  weak Int value <- 1
+  optional Int value2 <- `strong` value
+  \ Testing.checkTrue(present(value2))
+  \ Testing.checkTrue(`present` `strong` value)
+}
+
+unittest reduceUnary {
+  optional Formatted value <- `reduce<Int,Formatted>` 123
+  \ Testing.checkEquals(require(value).formatted(),"123")
 }
diff --git a/tests/value-init.0rt b/tests/value-init.0rt
--- a/tests/value-init.0rt
+++ b/tests/value-init.0rt
@@ -1,5 +1,5 @@
 /* -----------------------------------------------------------------------------
-Copyright 2020 Kevin P. Barry
+Copyright 2020-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.
@@ -164,5 +164,22 @@
 
   get () {
     return value
+  }
+}
+
+
+testcase "wrong base type" {
+  error
+  require "Value"
+  require "Int"
+}
+
+concrete Value {
+  @type create () -> (Int)
+}
+
+define Value {
+  create () {
+    return Int{ }
   }
 }
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.21.0.0
+version:             0.22.0.0
 synopsis:            Zeolite is a statically-typed, general-purpose programming language.
 
 description:
