diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,131 @@
 
 ---
 
+## [0.17.5] - 2026-08-19
+
+Point release on the 0.17.x line. Two language-surface changes lead: sum-type eliminators unify on
+`match` (scrutinee-first), and the primitive set is tentatively finalized — `strings.lines`/`unlines`
+are removed in favour of `hydra.formatting` term helpers. A symlink-aware filesystem family lands
+across all ten hosts, and Hydra's own build system continues its migration into Hydra.
+
+**Backward-incompatible.** The `cases` -> `match` rename and the `strings.lines`/`unlines` removal
+both change the published kernel surface. Code written against 0.17.4 that uses `Optionals.cases`
+or `strings.lines`/`strings.unlines` must be updated.
+
+### Highlights
+
+- **Sum-type eliminators unify on `match`** ([#615](https://github.com/CategoricalData/hydra/issues/615)):
+  the `cases`/`match` helper pair is flipped to a single scrutinee-first `match` convention across the
+  Haskell, Java, Python, and Scala DSLs, the `optionals.cases` -> `optionals.match` kernel primitive,
+  and the corresponding implementations in TypeScript and all four Lisp dialects.
+- **Primitive set tentatively finalized** ([#417](https://github.com/CategoricalData/hydra/issues/417)):
+  `strings.lines` and `strings.unlines` are removed as primitives; their semantics are preserved as
+  `hydra.formatting.lines`/`unlines` term helpers, with overlay implementations dropped across all ten
+  hosts and callers repointed.
+- **Symlink-aware filesystem primitives** ([#666](https://github.com/CategoricalData/hydra/issues/666)):
+  `status(followLinks)`, `createSymlink`, and `readSymlink` land with native implementations for every
+  host — Haskell, Java, Python, Scala, TypeScript, Clojure, Common Lisp, Scheme, and Emacs Lisp.
+- **Build system promotion continues** ([#416](https://github.com/CategoricalData/hydra/issues/416)):
+  adds `hydra.build.walk` extension-glob helpers (`extensionOf`/`filterByExtension`/`matchesExtension`)
+  and `hydra.build.comparereportlogic`, a set of pure snapshot-compare decision helpers.
+
+### Bug fixes
+
+- **`math.range` was inclusive of both bounds** ([#647](https://github.com/CategoricalData/hydra/issues/647)),
+  silently fabricating an extra element in index arithmetic.
+- **Java coder TCO-`cases` codegen** dropped enclosing-method type parameters from union-variant casts
+  (found via [#666](https://github.com/CategoricalData/hydra/issues/666)).
+- **`ShaclRdf` promoted to a DSL module** ([#652](https://github.com/CategoricalData/hydra/issues/652))
+  so the SHACL pipeline works outside Haskell.
+- **Cold-seed shim portability**: the R22 bootstrap shim used GNU-only `\+` and `\b`, which fail under
+  BSD `sed` on macOS ([#417](https://github.com/CategoricalData/hydra/issues/417)).
+- **Bootstrap Haskell target missing `unix`**: the demo's static `package.yaml` was not updated when
+  `System.Posix.Files` entered the kernel overlay, breaking every `*-to-haskell` bootstrap cell
+  ([#670](https://github.com/CategoricalData/hydra/issues/670)).
+- **Bootstrap comparison scored Scala and TypeScript cells as passing without comparing anything**:
+  `compare_output`'s target switch was missing both languages, so the baseline path was malformed,
+  every file counted as "no baseline found", and a zero-file comparison reported `pass`. A Scala or
+  TypeScript *target* cell could never fail ([#671](https://github.com/CategoricalData/hydra/issues/671)).
+
+### Known issues
+
+- A pre-#630 post-generation text pass rewrites `hydra.lib.<sub>` -> `hydra.overlay.<lang>.lib.<sub>`
+  in generated **doc comments** as well as code, because its quote-prefix guard does not recognize
+  mid-sentence prose references. Affects two kernel doc strings in `src/main`
+  (`hydra.error.system`, `hydra.error.packaging`) plus ~20 generated docstrings in the test tree;
+  cosmetic only, no behavioral impact. Tracked as part of
+  [#633](https://github.com/CategoricalData/hydra/issues/633).
+- Bootstrap comparison diff detail is not persisted to the run directory
+  ([#671](https://github.com/CategoricalData/hydra/issues/671)).
+
+---
+
+## [0.17.4] - 2026-08-09
+
+Point release on the 0.17.x line. Fixes an inference bug that mis-typed generated Java and Python for
+`readFile`, adds the `divide` primitive, and restores `hydra-ext` to the published package set. Release
+validation also surfaced a family of pre-existing cross-host code-generation defects — none of which the
+per-host CI jobs can catch, because only the bootstrap exercises one host generating *another*.
+
+### Highlights
+
+- **Output-only nominal generalization fixed** ([#637](https://github.com/CategoricalData/hydra/issues/637),
+  with [#638](https://github.com/CategoricalData/hydra/issues/638) /
+  [#639](https://github.com/CategoricalData/hydra/issues/639) /
+  [#640](https://github.com/CategoricalData/hydra/issues/640)): a nominal type appearing only in the
+  *output* position of a primitive's signature was generalized to a free type variable instead of resolving
+  as nominal, mis-typing the generated Java and Python for `readFile`. Also widens universe closure and adds
+  a `kernelTypeUniverse` helper, an effect DSL builder, and `effects`/`files`/`text`/`system` lib wrappers.
+- **`divide` primitive** ([#317](https://github.com/CategoricalData/hydra/issues/317)): now present in the
+  published kernel. This also clears the cold-clone dangle, since a cold seed against published 0.17.4
+  resolves `DefMath.divide`.
+- **`hydra-ext` republished** ([#636](https://github.com/CategoricalData/hydra/issues/636)): back in the
+  published set after its absence from 0.17.1–0.17.3, shipping to Hackage and PyPI.
+
+### Bug fixes
+
+- **Overlay-lib redirection skipped for the Haskell target**
+  ([#630](https://github.com/CategoricalData/hydra/issues/630)):
+  the Java, Python, Scheme, Clojure and Common Lisp heads resolved the overlay lib directory using an
+  all-lowercase path and did not lower-case the names they scanned. Haskell's overlay tree is capitalized
+  (`Hydra/Overlay/Haskell/Lib/`), so every `hydra.lib.*` redirect silently failed and the generated Haskell
+  imported `Hydra.Lib.*` instead of `Hydra.Overlay.Haskell.Lib.*`, producing hundreds of type errors. The
+  defect was invisible on case-sensitive filesystems, where the failed directory check falls back to a
+  hard-coded list that happens to be correct. The Python head carried *two* independent copies of this scan;
+  only the one in `generation.py` feeds the Haskell coder.
+- **Lisp-as-host bootstrap** ([#520](https://github.com/CategoricalData/hydra/issues/520),
+  [#630](https://github.com/CategoricalData/hydra/issues/630)): the four Lisp head decode drivers called
+  `from_json` with a stale 4-argument signature, and the Lisp bootstrap-driver coders were not pre-bound with
+  `overlaySubs`.
+- **Python-as-host dropped the `Graph` argument** ([#630](https://github.com/CategoricalData/hydra/issues/630)):
+  `overlaySubs` was not threaded into the Python head's `write_*` wrappers.
+- **Clojure `resolve-coder` and `decimal-to-float32`**
+  ([#630](https://github.com/CategoricalData/hydra/issues/630)): a stray parenthesis closed a `case` early,
+  making an unsupported-default block run unconditionally; and `(float (.doubleValue ...))` threw on
+  `Float.MAX_VALUE`, now saturating via `(.floatValue (bigdec x))`.
+- **Cold-seeder failed on macOS** ([#630](https://github.com/CategoricalData/hydra/issues/630)): the #376
+  cold seeder used a bare `sed -i -e`, which is GNU-only and aborts immediately under BSD `sed`.
+
+### Release engineering
+
+- **Review checkpoint before Maven Central publishes**
+  ([#591](https://github.com/CategoricalData/hydra/issues/591)): `HYDRA_JAVA_PUBLISH_HOLD=1` uploads the
+  aggregated Java deployment as `USER_MANAGED`, so it validates and then holds for review instead of
+  publishing automatically. This mirrors `HYDRA_SCALA_PUBLISH_HOLD` and makes an upload reversible — a held
+  deployment can be dropped, whereas an automatic one cannot be undone.
+
+### Known issues
+
+- **`hydra-ext` is not published to Maven Central**
+  ([#643](https://github.com/CategoricalData/hydra/issues/643)): it does not compile for the Java target,
+  because the visitor-pattern inner interface the Java coder emits collides with the enclosing `Visitor`
+  type in `hydra.cpp.syntax`. Only Java is affected; the Python coder emits sibling classes rather than
+  nested ones. ext ships to Hackage and PyPI at 0.17.4 and is expected to rejoin the Java set once fixed.
+- **TypeScript no longer self-hosts**
+  ([#642](https://github.com/CategoricalData/hydra/issues/642)): the TypeScript host's JSON decoder fails to
+  decode most of the kernel, so `typescript-as-host` code generation is broken. No published artifact is
+  affected — `dist/typescript` is generated by the Haskell host and passes its test suite.
+
 ## [0.17.3] - 2026-08-01
 
 Point release on the 0.17.x line, focused on **release-artifact integrity**. The 0.17.2 release shipped
diff --git a/hydra-java.cabal b/hydra-java.cabal
--- a/hydra-java.cabal
+++ b/hydra-java.cabal
@@ -1,11 +1,11 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.39.1.
+-- This file has been generated from package.yaml by hpack version 0.38.1.
 --
 -- see: https://github.com/sol/hpack
 
 name:           hydra-java
-version:        0.17.4
+version:        0.17.5
 synopsis:       Hydra's Java coder: emit Java source from Hydra modules
 description:    Hydra is an implementation of the LambdaGraph data model, which takes advantage of an isomorphism between labeled hypergraphs and typed lambda calculus: in Hydra, "graphs are programs, and programs are graphs". Java support for Hydra
 category:       Data
@@ -46,7 +46,7 @@
   build-depends:
       base >=4.19.0 && <4.22
     , containers >=0.6.7 && <0.8
-    , hydra-jvm ==0.17.4
-    , hydra-kernel ==0.17.4
+    , hydra-jvm ==0.17.5
+    , hydra-kernel ==0.17.5
     , scientific >=0.3.7 && <0.4
   default-language: Haskell2010
diff --git a/src/main/haskell/Hydra/Dsl/Java/Environment.hs b/src/main/haskell/Hydra/Dsl/Java/Environment.hs
--- a/src/main/haskell/Hydra/Dsl/Java/Environment.hs
+++ b/src/main/haskell/Hydra/Dsl/Java/Environment.hs
@@ -12,7 +12,7 @@
 import qualified Hydra.Packaging as Packaging
 import qualified Hydra.Typed as Typed
 import qualified Hydra.Typing as Typing
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 import qualified Data.Map as M
 import qualified Data.Set as S
diff --git a/src/main/haskell/Hydra/Dsl/Java/Language.hs b/src/main/haskell/Hydra/Dsl/Java/Language.hs
--- a/src/main/haskell/Hydra/Dsl/Java/Language.hs
+++ b/src/main/haskell/Hydra/Dsl/Java/Language.hs
@@ -29,7 +29,7 @@
 import qualified Hydra.Util as Util
 import qualified Hydra.Validation as Validation
 import qualified Hydra.Variants as Variants
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 import qualified Data.Set as S
 
diff --git a/src/main/haskell/Hydra/Dsl/Java/Syntax.hs b/src/main/haskell/Hydra/Dsl/Java/Syntax.hs
--- a/src/main/haskell/Hydra/Dsl/Java/Syntax.hs
+++ b/src/main/haskell/Hydra/Dsl/Java/Syntax.hs
@@ -7,7 +7,7 @@
 import qualified Hydra.Core as Core
 import qualified Hydra.Java.Syntax as Syntax
 import qualified Hydra.Typed as Typed
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 
 -- | DSL constructor for the hydra.java.syntax.AdditionalBound wrapper
diff --git a/src/main/haskell/Hydra/Gradle.hs b/src/main/haskell/Hydra/Gradle.hs
--- a/src/main/haskell/Hydra/Gradle.hs
+++ b/src/main/haskell/Hydra/Gradle.hs
@@ -7,7 +7,7 @@
 import qualified Hydra.Core as Core
 import qualified Hydra.File as File
 import qualified Hydra.Packaging as Packaging
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 
 -- | Configuration for the Gradle antlr plugin's grammar generation (generateGrammarSource).
diff --git a/src/main/haskell/Hydra/Java/Coder.hs b/src/main/haskell/Hydra/Java/Coder.hs
--- a/src/main/haskell/Hydra/Java/Coder.hs
+++ b/src/main/haskell/Hydra/Java/Coder.hs
@@ -69,7 +69,7 @@
 import qualified Hydra.Validation as Validation
 import qualified Hydra.Variables as Variables
 import qualified Hydra.Variants as Variants
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 import qualified Data.Map as M
 import qualified Data.Set as S
@@ -104,7 +104,7 @@
 
 annotateLambdaArgs :: Core.Name -> [Core.Type] -> [Core.Term] -> t0 -> Graph.Graph -> Either t1 [Core.Term]
 annotateLambdaArgs cname tApps argTerms cx g =
-    Logic.ifElse (Lists.null tApps) (Right argTerms) (Eithers.bind (Eithers.bind (Right (Lexical.lookupBinding g cname)) (\mel -> Optionals.cases mel (Right (Optionals.map (\prim -> Scoping.termSignatureToTypeScheme (Packaging.primitiveDefinitionSignature (Graph.primitiveDefinition prim))) (Maps.lookup cname (Graph.graphPrimitives g)))) (\el -> Right (Core.bindingTypeScheme el)))) (\mts -> Optionals.cases mts (Right argTerms) (\ts ->
+    Logic.ifElse (Lists.null tApps) (Right argTerms) (Eithers.bind (Eithers.bind (Right (Lexical.lookupBinding g cname)) (\mel -> Optionals.match mel (Right (Optionals.map (\prim -> Scoping.termSignatureToTypeScheme (Packaging.primitiveDefinitionSignature (Graph.primitiveDefinition prim))) (Maps.lookup cname (Graph.graphPrimitives g)))) (\el -> Right (Core.bindingTypeScheme el)))) (\mts -> Optionals.match mts (Right argTerms) (\ts ->
       let schemeType = Core.typeSchemeBody ts
           schemeTypeVars = collectTypeVars schemeType
           schemeVars = Lists.filter (\v -> Sets.member v schemeTypeVars) (Core.typeSchemeVariables ts)
@@ -139,7 +139,7 @@
         let inner = Core.annotatedTermBody v0
             ann = Annotations.getAnnotationMap (Core.annotatedTermAnnotation v0)
             ann_ =
-                    Optionals.cases (Maps.lookup Constants.keyType ann) ann (\typeTerm -> Eithers.either (\_ -> ann) (\t ->
+                    Optionals.match (Maps.lookup Constants.keyType ann) ann (\typeTerm -> Eithers.either (\_ -> ann) (\t ->
                       let t_ = substituteTypeVarsWithTypes subst t
                       in (Maps.insert Constants.keyType (EncodeCore.type_ t_) ann)) (DecodeCore.type_ cx typeTerm))
         in (Core.TermAnnotated (Core.AnnotatedTerm {
@@ -272,7 +272,7 @@
 
 bindingIsFunctionType :: Core.Binding -> Bool
 bindingIsFunctionType b =
-    Optionals.cases (Core.bindingTypeScheme b) (case (Strip.deannotateTerm (Core.bindingTerm b)) of
+    Optionals.match (Core.bindingTypeScheme b) (case (Strip.deannotateTerm (Core.bindingTerm b)) of
       Core.TermLambda _ -> True
       Core.TermProject _ -> True
       Core.TermCases _ -> True
@@ -319,7 +319,7 @@
                         deps = Pairs.second entry
                     in (key, (Sets.toList deps))) (Maps.toList allDeps))
           recursiveVars =
-                  Sets.fromList (Lists.concat (Lists.map (\names -> Logic.ifElse (Equality.equal (Lists.length names) 1) (Optionals.cases (Lists.head names) [] (\singleName -> Optionals.cases (Maps.lookup singleName allDeps) [] (\deps -> Logic.ifElse (Sets.member singleName deps) [
+                  Sets.fromList (Lists.concat (Lists.map (\names -> Logic.ifElse (Equality.equal (Lists.length names) 1) (Optionals.match (Lists.head names) [] (\singleName -> Optionals.match (Maps.lookup singleName allDeps) [] (\deps -> Logic.ifElse (Sets.member singleName deps) [
                     singleName] []))) names) sorted))
           thunkedVars =
                   Sets.fromList (Lists.concat (Lists.map (\b ->
@@ -378,8 +378,8 @@
             anns = Annotations.getAnnotationMap (Core.annotatedTermAnnotation v0)
             bodySubst = buildSubstFromAnnotations_go schemeVarSet g body
             annSubst =
-                    Optionals.cases (Maps.lookup Constants.keyType anns) Maps.empty (\typeTerm -> Eithers.either (\_ -> Maps.empty) (\annType -> case (Strip.deannotateTerm body) of
-                      Core.TermLambda v1 -> Optionals.cases (Core.lambdaDomain v1) Maps.empty (\dom -> case (Strip.deannotateType annType) of
+                    Optionals.match (Maps.lookup Constants.keyType anns) Maps.empty (\typeTerm -> Eithers.either (\_ -> Maps.empty) (\annType -> case (Strip.deannotateTerm body) of
+                      Core.TermLambda v1 -> Optionals.match (Core.lambdaDomain v1) Maps.empty (\dom -> case (Strip.deannotateType annType) of
                         Core.TypeFunction v2 -> buildTypeVarSubst schemeVarSet (Core.functionTypeDomain v2) dom
                         _ -> Maps.empty)
                       _ -> Maps.empty) (DecodeCore.type_ g typeTerm))
@@ -387,7 +387,7 @@
       Core.TermApplication v0 -> Maps.union (buildSubstFromAnnotations_go schemeVarSet g (Core.applicationFunction v0)) (buildSubstFromAnnotations_go schemeVarSet g (Core.applicationArgument v0))
       Core.TermLambda v0 -> buildSubstFromAnnotations_go schemeVarSet g (Core.lambdaBody v0)
       Core.TermCases v0 ->
-        let defSubst = Optionals.cases (Core.caseStatementDefault v0) Maps.empty (\d -> buildSubstFromAnnotations_go schemeVarSet g d)
+        let defSubst = Optionals.match (Core.caseStatementDefault v0) Maps.empty (\d -> buildSubstFromAnnotations_go schemeVarSet g d)
             caseSubsts =
                     Lists.foldl (\acc -> \fld -> Maps.union acc (buildSubstFromAnnotations_go schemeVarSet g (Core.caseAlternativeHandler fld))) Maps.empty (Core.caseStatementCases v0)
         in (Maps.union defSubst caseSubsts)
@@ -396,7 +396,7 @@
                 Lists.foldl (\acc -> \b -> Maps.union acc (buildSubstFromAnnotations_go schemeVarSet g (Core.bindingTerm b))) Maps.empty (Core.letBindings v0)
         in (Maps.union bindingSubst (buildSubstFromAnnotations_go schemeVarSet g (Core.letBody v0)))
       Core.TermList v0 -> Lists.foldl (\acc -> \t -> Maps.union acc (buildSubstFromAnnotations_go schemeVarSet g t)) Maps.empty v0
-      Core.TermOptional v0 -> Optionals.cases v0 Maps.empty (\t -> buildSubstFromAnnotations_go schemeVarSet g t)
+      Core.TermOptional v0 -> Optionals.match v0 Maps.empty (\t -> buildSubstFromAnnotations_go schemeVarSet g t)
       Core.TermPair v0 -> Maps.union (buildSubstFromAnnotations_go schemeVarSet g (Pairs.first v0)) (buildSubstFromAnnotations_go schemeVarSet g (Pairs.second v0))
       Core.TermRecord v0 -> Lists.foldl (\acc -> \fld -> Maps.union acc (buildSubstFromAnnotations_go schemeVarSet g (Core.fieldTerm fld))) Maps.empty (Core.recordFields v0)
       Core.TermSet v0 -> Lists.foldl (\acc -> \t -> Maps.union acc (buildSubstFromAnnotations_go schemeVarSet g t)) Maps.empty (Sets.toList v0)
@@ -499,7 +499,7 @@
 
 classifyDataReference :: Core.Name -> t0 -> Graph.Graph -> Either Errors.Error JavaEnvironment.JavaSymbolClass
 classifyDataReference name cx g =
-    Eithers.bind (Right (Lexical.lookupBinding g name)) (\mel -> Optionals.cases mel (Right JavaEnvironment.JavaSymbolClassLocalVariable) (\el -> Optionals.cases (Core.bindingTypeScheme el) (Left (Errors.ErrorOther (Errors.OtherError (Strings.concat2 "no type scheme for element " (Core.unName (Core.bindingName el)))))) (\ts -> Right (classifyDataTerm ts (Core.bindingTerm el)))))
+    Eithers.bind (Right (Lexical.lookupBinding g name)) (\mel -> Optionals.match mel (Right JavaEnvironment.JavaSymbolClassLocalVariable) (\el -> Optionals.match (Core.bindingTypeScheme el) (Left (Errors.ErrorOther (Errors.OtherError (Strings.concat2 "no type scheme for element " (Core.unName (Core.bindingName el)))))) (\ts -> Right (classifyDataTerm ts (Core.bindingTerm el)))))
 
 classifyDataTerm :: Core.TypeScheme -> Core.Term -> JavaEnvironment.JavaSymbolClass
 classifyDataTerm ts term =
@@ -550,7 +550,7 @@
 collectLambdaDomains :: Core.Term -> ([Core.Type], Core.Term)
 collectLambdaDomains t =
     case (Strip.deannotateTerm t) of
-      Core.TermLambda v0 -> Optionals.cases (Core.lambdaDomain v0) ([], t) (\dom ->
+      Core.TermLambda v0 -> Optionals.match (Core.lambdaDomain v0) ([], t) (\dom ->
         let rest = collectLambdaDomains (Core.lambdaBody v0)
         in (Lists.cons dom (Pairs.first rest), (Pairs.second rest)))
       _ -> ([], t)
@@ -590,7 +590,7 @@
 collectionTypeArgs label n aliases anns tyapps cx g =
     Logic.ifElse (Logic.not (Lists.null tyapps)) (takeTypeArgs label n tyapps cx g) (
       let combinedAnns = Lists.foldl (\acc -> \m -> Maps.union acc m) Maps.empty anns
-      in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mtyp -> Eithers.bind (Optionals.cases mtyp (Right []) (\typ -> case (Strip.deannotateType typ) of
+      in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mtyp -> Eithers.bind (Optionals.match mtyp (Right []) (\typ -> case (Strip.deannotateType typ) of
         Core.TypeList v0 -> Right [
           v0]
         Core.TypeSet v0 -> Right [
@@ -717,7 +717,7 @@
 
       let ns = Packaging.moduleName mod
           parentNs = namespaceParent ns
-          pkg = Optionals.cases parentNs (Utils.javaPackageDeclaration ns) (\pns -> Utils.javaPackageDeclaration pns)
+          pkg = Optionals.match parentNs (Utils.javaPackageDeclaration ns) (\pns -> Utils.javaPackageDeclaration pns)
           mods = [
                 Syntax.InterfaceModifierPublic]
           className = elementsClassName ns
@@ -753,7 +753,7 @@
 
 correctTypeApps :: t0 -> Core.Name -> [Core.Term] -> [Core.Type] -> t1 -> Graph.Graph -> Either Errors.Error [Core.Type]
 correctTypeApps gr name args fallbackTypeApps cx g =
-    Eithers.bind (Right (Lexical.lookupBinding g name)) (\mel -> Optionals.cases mel (Right fallbackTypeApps) (\el -> Optionals.cases (Core.bindingTypeScheme el) (Right fallbackTypeApps) (\ts ->
+    Eithers.bind (Right (Lexical.lookupBinding g name)) (\mel -> Optionals.match mel (Right fallbackTypeApps) (\el -> Optionals.match (Core.bindingTypeScheme el) (Right fallbackTypeApps) (\ts ->
       let schemeType = Core.typeSchemeBody ts
           allSchemeVars = Lists.filter (\v -> isSimpleName v) (Core.typeSchemeVariables ts)
           schemeTypeVars = collectTypeVars schemeType
@@ -781,7 +781,7 @@
           peeled = peelDomainTypes (Lists.length args) schemeType
           schemeDoms = Pairs.first peeled
       in (Eithers.bind (Eithers.mapList (\arg -> Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g (Annotations.termAnnotationInternal arg))) args) (\mArgTypes -> Logic.ifElse (Logic.not (Lists.null (Lists.filter (\m -> Optionals.isNone m) mArgTypes))) (Right fallbackTypeApps) (
-        let argTypes = Lists.bind mArgTypes (\m -> Optionals.cases m [] (\x -> Lists.pure x))
+        let argTypes = Lists.bind mArgTypes (\m -> Optionals.match m [] (\x -> Lists.pure x))
             irDoms = Lists.map (\d -> applySubstSimple irSubst d) schemeDoms
             domsMatch =
                     Lists.null (Lists.filter (\p -> Logic.not (typesMatch (Strip.deannotateType (Pairs.first p)) (Strip.deannotateType (Pairs.second p)))) (Lists.zip irDoms argTypes))
@@ -802,7 +802,7 @@
     Eithers.bind (Eithers.mapList (\f -> recordMemberVar aliases f cx g) fields) (\memberVars -> Eithers.bind (Eithers.mapList (\p -> addComment (Pairs.first p) (Pairs.second p) cx g) (Lists.zip memberVars fields)) (\memberVars_ ->
       let elNameStr = Syntax.unIdentifier (Utils.nameToJavaName aliases elName)
           linkTargetStr =
-                  Optionals.cases parentName elNameStr (\pn -> Strings.concat2 (Strings.concat2 (Syntax.unIdentifier (Utils.nameToJavaName aliases pn)) ".") (Utils.sanitizeJavaName (Util.qualifiedNameLocal (Names.qualifyName elName))))
+                  Optionals.match parentName elNameStr (\pn -> Strings.concat2 (Strings.concat2 (Syntax.unIdentifier (Utils.nameToJavaName aliases pn)) ".") (Utils.sanitizeJavaName (Util.qualifiedNameLocal (Names.qualifyName elName))))
       in (Eithers.bind (Logic.ifElse (Ordering.gt (Lists.length fields) 1) (Eithers.mapList (\f -> Eithers.bind (recordWithMethod aliases elName fields f cx g) (\decl ->
         let fname = Core.unName (Core.fieldTypeName f)
             comment =
@@ -814,7 +814,7 @@
                       "} replaced."]
         in (Right (withCommentString comment decl)))) fields) (Right [])) (\withMethods -> Eithers.bind (recordConstructor aliases elName fields cx g) (\cons -> Eithers.bind (Eithers.mapList (\f ->
         let fname = Utils.sanitizeJavaName (Core.unName (Core.fieldTypeName f))
-        in (Eithers.bind (Annotations.commentsFromFieldType cx g f) (\mDoc -> Right (Optionals.cases mDoc "" (\d -> Strings.concat [
+        in (Eithers.bind (Annotations.commentsFromFieldType cx g f) (\mDoc -> Right (Optionals.match mDoc "" (\d -> Strings.concat [
           "@param ",
           fname,
           " ",
@@ -833,12 +833,13 @@
             consWithComment = withCommentString consComment cons
         in (Eithers.bind (Logic.ifElse isInner (Right []) (Eithers.bind (constantDeclForTypeName aliases elName cx g) (\d -> Eithers.bind (Eithers.mapList (\f -> constantDeclForFieldType elName aliases f cx g) fields) (\dfields -> Right (Lists.cons d dfields))))) (\tn -> Eithers.bind (Logic.ifElse isInner (Right []) (recordBuilderClass aliases tparams elName fields cx g)) (\builderDecls ->
           let comparableMethods =
-                  Optionals.cases parentName (Logic.ifElse (Logic.and (Logic.not isInner) isSer) [
+                  Optionals.match parentName (Logic.ifElse (Logic.and (Logic.not isInner) isSer) [
                     recordCompareToMethod aliases tparams elName fields] []) (\pn -> Logic.ifElse isSer (variantCompareToMethod aliases tparams pn elName (Optionals.withDefault 0 ordinal) fields) [])
               noCommentMethods =
                       Lists.map (\x -> noComment x) (Lists.concat2 [
                         recordEqualsMethod aliases elName fields,
-                        (recordHashCodeMethod fields)] comparableMethods)
+                        (recordHashCodeMethod fields),
+                        (recordToStringMethod elName fields)] comparableMethods)
               bodyDecls =
                       Lists.concat [
                         tn,
@@ -864,7 +865,7 @@
                       Core.fieldTypeName = (Core.Name "value"),
                       Core.fieldTypeType = (Strip.deannotateType ftype)}]
           varName = Utils.variantClassName False elName fname
-      in (Eithers.bind (declarationForRecordType_ True isSer aliases [] varName (Logic.ifElse isSer (Just elName) Nothing) (Logic.ifElse isSer (Just ordinal) Nothing) rfields cx g) (\innerDecl -> Right (augmentVariantClass aliases tparams elName innerDecl)))) (Lists.zip fields (Math.range 0 (Math.sub (Lists.length fields) 1)))) (\variantClasses ->
+      in (Eithers.bind (declarationForRecordType_ True isSer aliases [] varName (Logic.ifElse isSer (Just elName) Nothing) (Logic.ifElse isSer (Just ordinal) Nothing) rfields cx g) (\innerDecl -> Right (augmentVariantClass aliases tparams elName innerDecl)))) (Lists.zip fields (Math.range 0 (Lists.length fields)))) (\variantClasses ->
       let variantDecls =
               Lists.map (\vc -> Syntax.ClassBodyDeclarationClassMember (Syntax.ClassMemberDeclarationClass vc)) variantClasses
       in (Eithers.bind (Eithers.mapList (\pair -> addComment (Pairs.first pair) (Pairs.second pair) cx g) (Lists.zip variantDecls fields)) (\variantDecls_ ->
@@ -1056,10 +1057,10 @@
                     _ -> Nothing
           directRefSubst = directRefSubstitution directInputVars codVar groupedDirect
           codSubst =
-                  Optionals.cases (findPairFirst cod) Maps.empty (\cv -> Logic.ifElse (Maps.member cv selfRefSubst) Maps.empty (Optionals.cases (findSelfRefVar groupedByInput) Maps.empty (\refVar -> Logic.ifElse (Equality.equal cv refVar) Maps.empty (Maps.singleton cv refVar))))
+                  Optionals.match (findPairFirst cod) Maps.empty (\cv -> Logic.ifElse (Maps.member cv selfRefSubst) Maps.empty (Optionals.match (findSelfRefVar groupedByInput) Maps.empty (\refVar -> Logic.ifElse (Equality.equal cv refVar) Maps.empty (Maps.singleton cv refVar))))
           domVars = Sets.fromList (Lists.bind doms (\d -> Sets.toList (collectTypeVars d)))
           danglingSubst =
-                  Optionals.cases (findPairFirst cod) Maps.empty (\cv -> Logic.ifElse (Sets.member cv domVars) Maps.empty (Optionals.cases (findSelfRefVar groupedByInput) Maps.empty (\refVar -> Maps.singleton cv (Core.TypeVariable refVar))))
+                  Optionals.match (findPairFirst cod) Maps.empty (\cv -> Logic.ifElse (Sets.member cv domVars) Maps.empty (Optionals.match (findSelfRefVar groupedByInput) Maps.empty (\refVar -> Maps.singleton cv (Core.TypeVariable refVar))))
       in (Maps.union (Maps.union (Maps.union (nameMapToTypeMap selfRefSubst) (nameMapToTypeMap codSubst)) danglingSubst) (nameMapToTypeMap directRefSubst))
 
 directRefSubstitution :: (Eq t0, Ord t0) => (S.Set t0 -> Maybe t0 -> M.Map t0 [t0] -> M.Map t0 t0)
@@ -1088,7 +1089,7 @@
           ns_ = Util.qualifiedNameModuleName qn
           local = Util.qualifiedNameLocal qn
           sep = Logic.ifElse isMethod "::" "."
-      in (Logic.ifElse isPrim (Syntax.Identifier (Strings.concat2 (Strings.concat2 (elementJavaIdentifier_qualify aliases ns_ (Formatting.capitalize local)) ".") JavaNames.applyMethodName)) (Optionals.cases ns_ (Syntax.Identifier (Utils.sanitizeJavaName local)) (\n -> Syntax.Identifier (Strings.concat2 (Strings.concat2 (elementJavaIdentifier_qualify aliases (namespaceParent n) (elementsClassName n)) sep) (Utils.sanitizeJavaName local)))))
+      in (Logic.ifElse isPrim (Syntax.Identifier (Strings.concat2 (Strings.concat2 (elementJavaIdentifier_qualify aliases ns_ (Formatting.capitalize local)) ".") JavaNames.applyMethodName)) (Optionals.match ns_ (Syntax.Identifier (Utils.sanitizeJavaName local)) (\n -> Syntax.Identifier (Strings.concat2 (Strings.concat2 (elementJavaIdentifier_qualify aliases (namespaceParent n) (elementsClassName n)) sep) (Utils.sanitizeJavaName local)))))
 
 elementJavaIdentifier_qualify :: JavaEnvironment.Aliases -> Maybe Packaging.ModuleName -> String -> String
 elementJavaIdentifier_qualify aliases mns s =
@@ -1118,14 +1119,14 @@
           fun = Pairs.first gathered
           args = Pairs.first (Pairs.second gathered)
           typeApps = Pairs.second (Pairs.second gathered)
-      in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g (Annotations.termAnnotationInternal fun))) (\mfunTyp -> Eithers.bind (Optionals.cases mfunTyp (Checking.typeOfTerm cx g fun) (\t -> Right t)) (\funTyp ->
+      in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g (Annotations.termAnnotationInternal fun))) (\mfunTyp -> Eithers.bind (Optionals.match mfunTyp (Checking.typeOfTerm cx g fun) (\t -> Right t)) (\funTyp ->
         let arity = Arity.typeArity funTyp
             deannotatedFun = Strip.deannotateTerm fun
             calleeName =
                     case deannotatedFun of
                       Core.TermVariable v0 -> Just v0
                       _ -> Nothing
-        in (Eithers.bind (Optionals.cases calleeName (Right args) (\cname -> annotateLambdaArgs cname typeApps args cx g)) (\annotatedArgs -> case deannotatedFun of
+        in (Eithers.bind (Optionals.match calleeName (Right args) (\cname -> annotateLambdaArgs cname typeApps args cx g)) (\annotatedArgs -> case deannotatedFun of
           Core.TermVariable v0 -> Logic.ifElse (Optionals.isGiven (Maps.lookup v0 (Graph.graphPrimitives g))) (
             let hargs = Lists.take arity annotatedArgs
                 rargs = Lists.drop arity annotatedArgs
@@ -1147,14 +1148,14 @@
 
 encodeApplication_fallback :: JavaEnvironment.JavaEnvironment -> JavaEnvironment.Aliases -> Graph.Graph -> [Core.Type] -> Core.Term -> Core.Term -> Typing.InferenceContext -> Graph.Graph -> Either Errors.Error Syntax.Expression
 encodeApplication_fallback env aliases gr typeApps lhs rhs cx g =
-    Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g (Annotations.termAnnotationInternal lhs))) (\mt -> Eithers.bind (Optionals.cases mt (Checking.typeOfTerm cx g lhs) (\typ -> Right typ)) (\t -> case (Strip.deannotateTypeParameters (Strip.deannotateType t)) of
+    Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g (Annotations.termAnnotationInternal lhs))) (\mt -> Eithers.bind (Optionals.match mt (Checking.typeOfTerm cx g lhs) (\typ -> Right typ)) (\t -> case (Strip.deannotateTypeParameters (Strip.deannotateType t)) of
       Core.TypeFunction v0 ->
         let dom = Core.functionTypeDomain v0
             cod = Core.functionTypeCodomain v0
             defaultExpr =
                     \_bug438_dflt -> Eithers.bind (encodeTerm env lhs cx g) (\jfun -> Eithers.bind (encodeTerm env rhs cx g) (\jarg -> Right (applyJavaArg jfun jarg)))
             elimBranch =
-                    \_bug438_elim -> Eithers.bind (encodeTerm env rhs cx g) (\jarg -> Eithers.bind (Logic.ifElse (Logic.not (Lists.null (javaTypeArgumentsForType dom))) (Right dom) (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g (Annotations.termAnnotationInternal rhs))) (\mrt -> Optionals.cases mrt (Eithers.bind (Checking.typeOfTerm cx g rhs) (\rt -> Right (Logic.ifElse (Logic.not (Lists.null (javaTypeArgumentsForType rt))) rt dom))) (\rt -> Right (Logic.ifElse (Logic.not (Lists.null (javaTypeArgumentsForType rt))) rt dom))))) (\enrichedDom -> encodeElimination env (Just jarg) enrichedDom cod (Strip.deannotateTerm lhs) cx g))
+                    \_bug438_elim -> Eithers.bind (encodeTerm env rhs cx g) (\jarg -> Eithers.bind (Logic.ifElse (Logic.not (Lists.null (javaTypeArgumentsForType dom))) (Right dom) (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g (Annotations.termAnnotationInternal rhs))) (\mrt -> Optionals.match mrt (Eithers.bind (Checking.typeOfTerm cx g rhs) (\rt -> Right (Logic.ifElse (Logic.not (Lists.null (javaTypeArgumentsForType rt))) rt dom))) (\rt -> Right (Logic.ifElse (Logic.not (Lists.null (javaTypeArgumentsForType rt))) rt dom))))) (\enrichedDom -> encodeElimination env (Just jarg) enrichedDom cod (Strip.deannotateTerm lhs) cx g))
         in case (Strip.deannotateAndDetypeTerm lhs) of
           Core.TermProject _ -> elimBranch ()
           Core.TermCases _ -> elimBranch ()
@@ -1188,7 +1189,7 @@
       in case (Strip.deannotateAndDetypeTerm elimTerm) of
         Core.TermProject v0 ->
           let fname = Core.projectionFieldName v0
-          in (Eithers.bind (encodeType aliases Sets.empty dom cx g) (\jdom0 -> Eithers.bind (Utils.javaTypeToJavaReferenceType jdom0 cx) (\_ -> Optionals.cases marg (
+          in (Eithers.bind (encodeType aliases Sets.empty dom cx g) (\jdom0 -> Eithers.bind (Utils.javaTypeToJavaReferenceType jdom0 cx) (\_ -> Optionals.match marg (
             let projVar = Core.Name "projected"
                 jbody =
                         Utils.javaExpressionNameToJavaExpression (Utils.fieldExpression (Utils.variableToJavaIdentifier projVar) (Utils.javaIdentifier (Core.unName fname)))
@@ -1201,7 +1202,7 @@
           let tname = Core.caseStatementTypeName v0
               def_ = Core.caseStatementDefault v0
               fields = Core.caseStatementCases v0
-          in (Optionals.cases marg (
+          in (Optionals.match marg (
             let uVar = Core.Name "u"
                 domTypeArgs0 =
                         \ty -> \acc -> case (Strip.deannotateType ty) of
@@ -1227,7 +1228,7 @@
             in (Eithers.bind (encodeType aliases Sets.empty effectiveCod cx g) (\jcod -> Eithers.bind (Utils.javaTypeToJavaReferenceType jcod cx) (\rt -> Eithers.bind (domTypeArgs aliases dom cx g) (\domArgs ->
               let targs = typeArgsOrDiamond (Lists.concat2 domArgs [
                     Syntax.TypeArgumentReference rt])
-              in (Eithers.bind (Optionals.cases def_ (Right []) (\d -> Eithers.bind (otherwiseBranch env aliases dom cod tname jcod domArgs d cx g) (\b -> Right [
+              in (Eithers.bind (Optionals.match def_ (Right []) (\d -> Eithers.bind (otherwiseBranch env aliases dom cod tname jcod domArgs d cx g) (\b -> Right [
                 b]))) (\otherwiseBranches -> Eithers.bind (Eithers.mapList (\f -> visitBranch env aliases dom tname jcod domArgs f cx g) fields) (\visitBranches ->
                 let body = Syntax.ClassBody (Lists.concat2 otherwiseBranches visitBranches)
                     visitor = Utils.javaConstructorCall (Utils.javaConstructorName consId (Just targs)) [] (Just body)
@@ -1238,7 +1239,7 @@
                   \ja -> Utils.javaFieldAccessToJavaExpression (Syntax.FieldAccess {
                     Syntax.fieldAccessQualifier = (Syntax.FieldAccess_QualifierPrimary (Utils.javaExpressionToJavaPrimary ja)),
                     Syntax.fieldAccessIdentifier = (Utils.javaIdentifier JavaNames.valueFieldName)})
-          in (Right (Optionals.cases marg (
+          in (Right (Optionals.match marg (
             let wVar = Core.Name "wrapped"
                 wArg = Utils.javaIdentifierToJavaExpression (Utils.variableToJavaIdentifier wVar)
             in (Utils.javaLambda wVar (withArg wArg))) (\jarg -> withArg jarg)))
@@ -1293,7 +1294,7 @@
 encodeFunctionFormTerm env anns term cx g =
 
       let combinedAnns = Lists.foldl (\acc -> \m -> Maps.union acc m) Maps.empty anns
-      in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mt -> Eithers.bind (Optionals.cases mt (Optionals.cases (tryInferFunctionType term) (Checking.typeOfTerm cx g term) (\inferredType -> Right inferredType)) (\t -> Right t)) (\typ -> case (Strip.deannotateType typ) of
+      in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mt -> Eithers.bind (Optionals.match mt (Optionals.match (tryInferFunctionType term) (Checking.typeOfTerm cx g term) (\inferredType -> Right inferredType)) (\t -> Right t)) (\typ -> case (Strip.deannotateType typ) of
         Core.TypeFunction v0 -> encodeFunction env (Core.functionTypeDomain v0) (Core.functionTypeCodomain v0) term cx g
         _ -> encodeNullaryConstant env typ term cx g)))
 
@@ -1313,7 +1314,7 @@
         className,
         "::",
         JavaNames.applyMethodName])))) (
-        let paramNames = Lists.map (\i -> Core.Name (Strings.concat2 "p" (Literals.showInt32 i))) (Math.range 0 (Math.sub arity 1))
+        let paramNames = Lists.map (\i -> Core.Name (Strings.concat2 "p" (Literals.showInt32 i))) (Math.range 0 arity)
             paramExprs = Lists.map (\p -> Utils.javaIdentifierToJavaExpression (Utils.variableToJavaIdentifier p)) paramNames
             classId = Syntax.Identifier className
             call =
@@ -1466,7 +1467,7 @@
           term0 = Packaging.termDefinitionBody tdef
       in (Eithers.bind (Annotations.getTermDescription cx g term0) (\mDoc ->
         let ts =
-                Optionals.cases (Packaging.termDefinitionSignature tdef) (Core.TypeScheme {
+                Optionals.match (Packaging.termDefinitionSignature tdef) (Core.TypeScheme {
                   Core.typeSchemeVariables = [],
                   Core.typeSchemeBody = (Core.TypeVariable (Core.Name "hydra.core.Unit")),
                   Core.typeSchemeConstraints = Nothing}) (\sig -> Scoping.termSignatureToTypeScheme sig)
@@ -1537,7 +1538,7 @@
                       mods = [
                             Syntax.InterfaceMethodModifierStatic]
                       jname = Utils.sanitizeJavaName (Formatting.decapitalize (Names.localNameOf name))
-                      isTCO = False
+                      isTCO = Logic.and (Logic.not (Lists.null params)) (Analysis.isSelfTailRecursive name body)
                   in (Eithers.bind (Logic.ifElse isTCO (
                     let tcoSuffix = "_tco"
                         snapshotNames = Lists.map (\p -> Core.Name (Strings.concat2 (Core.unName p) tcoSuffix)) params
@@ -1548,7 +1549,7 @@
                                 Logic.ifElse (Lists.null bindings) annotatedBody (Core.TermLet (Core.Let {
                                   Core.letBindings = bindings,
                                   Core.letBody = annotatedBody}))
-                    in (Eithers.bind (encodeTermTCO env2WithTypeParams name params tcoVarRenames 0 tcoBody cx g) (\tcoStmts ->
+                    in (Eithers.bind (encodeTermTCO env2WithTypeParams name params tparams tcoVarRenames 0 tcoBody cx g) (\tcoStmts ->
                       let whileBodyStmts = Lists.concat2 snapshotDecls tcoStmts
                           whileBodyBlock =
                                   Syntax.StatementWithoutTrailing (Syntax.StatementWithoutTrailingSubstatementBlock (Syntax.Block whileBodyStmts))
@@ -1563,7 +1564,7 @@
                     in (Right (Lists.concat2 bindingStmts [
                       returnSt]))))) (\methodBody ->
                     let imdMember = Utils.interfaceMethodDeclaration mods jparams jname jformalParams result (Just methodBody)
-                    in (Right (Optionals.cases mDoc (noInterfaceComment imdMember) (\doc -> withInterfaceCommentString doc imdMember))))))))))))))))))
+                    in (Right (Optionals.match mDoc (noInterfaceComment imdMember) (\doc -> withInterfaceCommentString doc imdMember))))))))))))))))))
 
 encodeTermInternal :: JavaEnvironment.JavaEnvironment -> [M.Map Core.Name Core.Term] -> [Syntax.Type] -> Core.Term -> Typing.InferenceContext -> Graph.Graph -> Either Errors.Error Syntax.Expression
 encodeTermInternal env anns tyapps term cx g0 =
@@ -1586,10 +1587,10 @@
                           let annotated = Annotations.setTermAnnotation Constants.keyType (Just (EncodeCore.type_ branchType)) t1
                           in (encodeTermInternal env anns [] annotated cx g)
                 eitherCall =
-                        \methodName -> \expr -> Optionals.cases mtargs (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStatic (Syntax.Identifier "hydra.overlay.java.util.Either") (Syntax.Identifier methodName) [
+                        \methodName -> \expr -> Optionals.match mtargs (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStatic (Syntax.Identifier "hydra.overlay.java.util.Either") (Syntax.Identifier methodName) [
                           expr])) (\targs -> Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStaticWithTypeArgs (Syntax.Identifier "hydra.overlay.java.util.Either") (Syntax.Identifier methodName) targs [
                           expr]))
-            in (Eithers.either (\term1 -> Eithers.bind (Optionals.cases branchTypes (encode term1) (\bt -> encodeWithType (Pairs.first bt) term1)) (\expr -> Right (eitherCall "left" expr))) (\term1 -> Eithers.bind (Optionals.cases branchTypes (encode term1) (\bt -> encodeWithType (Pairs.second bt) term1)) (\expr -> Right (eitherCall "right" expr))) v0))))
+            in (Eithers.either (\term1 -> Eithers.bind (Optionals.match branchTypes (encode term1) (\bt -> encodeWithType (Pairs.first bt) term1)) (\expr -> Right (eitherCall "left" expr))) (\term1 -> Eithers.bind (Optionals.match branchTypes (encode term1) (\bt -> encodeWithType (Pairs.second bt) term1)) (\expr -> Right (eitherCall "right" expr))) v0))))
         Core.TermLambda _ -> encodeFunctionFormTerm env anns term cx g
         Core.TermProject _ -> encodeFunctionFormTerm env anns term cx g
         Core.TermCases _ -> encodeFunctionFormTerm env anns term cx g
@@ -1611,7 +1612,7 @@
                   combinedAnns = Lists.foldl (\acc -> \m -> Maps.union acc m) Maps.empty anns
                   g2 = JavaEnvironment.javaEnvironmentGraph env2
                   aliases2 = JavaEnvironment.javaEnvironmentAliases env2
-              in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mt -> Eithers.bind (Optionals.cases mt (Checking.typeOfTerm cx g2 body) (\t -> Right t)) (\letType -> Eithers.bind (encodeType aliases2 Sets.empty letType cx g) (\jLetType -> Eithers.bind (Utils.javaTypeToJavaReferenceType jLetType cx) (\rt ->
+              in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mt -> Eithers.bind (Optionals.match mt (Checking.typeOfTerm cx g2 body) (\t -> Right t)) (\letType -> Eithers.bind (encodeType aliases2 Sets.empty letType cx g) (\jLetType -> Eithers.bind (Utils.javaTypeToJavaReferenceType jLetType cx) (\rt ->
                 let supplierRt =
                         Syntax.ReferenceTypeClassOrInterface (Syntax.ClassOrInterfaceTypeClass (Utils.javaClassType [
                           rt] JavaNames.javaUtilFunctionPackageName "Supplier"))
@@ -1626,7 +1627,7 @@
                     Pairs.first kv,
                     (Pairs.second kv)])) (Lists.zip jkeys jvals)
           in (Eithers.bind (collectionTypeArgs "map" 2 aliases anns tyapps cx g) (\targs -> Right (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStaticWithTypeArgs (Syntax.Identifier "hydra.overlay.java.util.PersistentMap") (Syntax.Identifier "ofEntries") targs pairExprs)))))))
-        Core.TermOptional v0 -> Optionals.cases v0 (Logic.ifElse (Lists.null tyapps) (Right (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStatic (Syntax.Identifier "hydra.overlay.java.util.Optional") (Syntax.Identifier "none") []))) (Eithers.bind (takeTypeArgs "optional" 1 tyapps cx g) (\targs -> Right (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStaticWithTypeArgs (Syntax.Identifier "hydra.overlay.java.util.Optional") (Syntax.Identifier "none") targs []))))) (\term1 -> Eithers.bind (encode term1) (\expr -> Eithers.bind (collectionTypeArgs "optional" 1 aliases anns tyapps cx g) (\targs -> Right (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStaticWithTypeArgs (Syntax.Identifier "hydra.overlay.java.util.Optional") (Syntax.Identifier "given") targs [
+        Core.TermOptional v0 -> Optionals.match v0 (Logic.ifElse (Lists.null tyapps) (Right (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStatic (Syntax.Identifier "hydra.overlay.java.util.Optional") (Syntax.Identifier "none") []))) (Eithers.bind (takeTypeArgs "optional" 1 tyapps cx g) (\targs -> Right (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStaticWithTypeArgs (Syntax.Identifier "hydra.overlay.java.util.Optional") (Syntax.Identifier "none") targs []))))) (\term1 -> Eithers.bind (encode term1) (\expr -> Eithers.bind (collectionTypeArgs "optional" 1 aliases anns tyapps cx g) (\targs -> Right (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStaticWithTypeArgs (Syntax.Identifier "hydra.overlay.java.util.Optional") (Syntax.Identifier "given") targs [
           expr])))))
         Core.TermPair v0 -> Eithers.bind (encode (Pairs.first v0)) (\jterm1 -> Eithers.bind (encode (Pairs.second v0)) (\jterm2 -> Eithers.bind (Logic.ifElse (Lists.null tyapps) (Right Nothing) (Eithers.bind (Eithers.mapList (\jt -> Utils.javaTypeToJavaReferenceType jt cx) tyapps) (\rts -> Right (Just (Syntax.TypeArgumentsOrDiamondArguments (Lists.map (\rt -> Syntax.TypeArgumentReference rt) rts)))))) (\mtargs -> Right (Utils.javaConstructorCall (Utils.javaConstructorName (Syntax.Identifier "hydra.overlay.java.util.Pair") mtargs) [
           jterm1,
@@ -1647,10 +1648,10 @@
                           params = collectForallParams (Strip.deannotateType recTyp)
                       in (Logic.ifElse (Logic.or (Lists.null args) (Logic.not (Equality.equal (Lists.length args) (Lists.length params)))) Nothing (Just (Maps.fromList (Lists.zip params args))))))
                 encodeField =
-                        \fld -> Optionals.cases mFieldTypeMap (encode (Core.fieldTerm fld)) (\ftmap ->
+                        \fld -> Optionals.match mFieldTypeMap (encode (Core.fieldTerm fld)) (\ftmap ->
                           let mftyp = Maps.lookup (Core.fieldName fld) ftmap
-                          in (Optionals.cases mftyp (encode (Core.fieldTerm fld)) (\ftyp ->
-                            let resolvedType = Optionals.cases mTypeSubst ftyp (\subst -> applySubstFull subst ftyp)
+                          in (Optionals.match mftyp (encode (Core.fieldTerm fld)) (\ftyp ->
+                            let resolvedType = Optionals.match mTypeSubst ftyp (\subst -> applySubstFull subst ftyp)
                                 annotatedFieldTerm =
                                         Annotations.setTermAnnotation Constants.keyType (Just (EncodeCore.type_ resolvedType)) (Core.fieldTerm fld)
                             in (encodeTermInternal env anns [] annotatedFieldTerm cx g))))
@@ -1658,7 +1659,7 @@
               let consId = Utils.nameToJavaName aliases recName
               in (Eithers.bind (Logic.ifElse (Logic.not (Lists.null tyapps)) (Eithers.bind (Eithers.mapList (\jt -> Utils.javaTypeToJavaReferenceType jt cx) tyapps) (\rts -> Right (Just (Syntax.TypeArgumentsOrDiamondArguments (Lists.map (\rt -> Syntax.TypeArgumentReference rt) rts))))) (
                 let combinedAnns = Lists.foldl (\acc -> \m -> Maps.union acc m) Maps.empty anns
-                in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mtyp -> Optionals.cases mtyp (Right Nothing) (\annTyp ->
+                in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mtyp -> Optionals.match mtyp (Right Nothing) (\annTyp ->
                   let typeArgs = extractTypeApplicationArgs (Strip.deannotateType annTyp)
                   in (Logic.ifElse (Lists.null typeArgs) (Right Nothing) (Eithers.bind (Eithers.mapList (\t -> Eithers.bind (encodeType aliases Sets.empty t cx g) (\jt -> Utils.javaTypeToJavaReferenceType jt cx)) typeArgs) (\jTypeArgs -> Right (Just (Syntax.TypeArgumentsOrDiamondArguments (Lists.map (\rt -> Syntax.TypeArgumentReference rt) jTypeArgs))))))))))) (\mtargs -> Right (Utils.javaConstructorCall (Utils.javaConstructorName consId mtargs) fieldExprs Nothing)))))))
         Core.TermSet v0 -> Logic.ifElse (Sets.null v0) (Logic.ifElse (Lists.null tyapps) (Right (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStatic (Syntax.Identifier "hydra.overlay.java.util.PersistentSet") (Syntax.Identifier "empty") []))) (Eithers.bind (takeTypeArgs "set" 1 tyapps cx g) (\targs -> Right (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStaticWithTypeArgs (Syntax.Identifier "hydra.overlay.java.util.PersistentSet") (Syntax.Identifier "empty") targs []))))) (
@@ -1668,7 +1669,7 @@
           let combinedAnns = Lists.foldl (\acc -> \m -> Maps.union acc m) Maps.empty anns
           in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mtyp ->
             let annotatedBody =
-                    Optionals.cases mtyp (Core.typeLambdaBody v0) (\t -> case t of
+                    Optionals.match mtyp (Core.typeLambdaBody v0) (\t -> case t of
                       Core.TypeForall v1 -> Annotations.setTermAnnotation Constants.keyType (Just (EncodeCore.type_ (Core.forallTypeBody v1))) (Core.typeLambdaBody v0)
                       _ -> Core.typeLambdaBody v0)
             in (encodeTerm env2 annotatedBody cx g))))
@@ -1685,9 +1686,9 @@
                         (Utils.sanitizeJavaName (Formatting.capitalize (Core.unName injFieldName)))])
           in (Eithers.bind (isFieldUnitType injTypeName injFieldName cx g) (\fieldIsUnit -> Eithers.bind (Logic.ifElse (Logic.or (Predicates.isUnitTerm (Strip.deannotateTerm injFieldTerm)) fieldIsUnit) (Right []) (Eithers.bind (encode injFieldTerm) (\ex -> Right [
             ex]))) (\args -> Right (Utils.javaConstructorCall (Utils.javaConstructorName consId Nothing) args Nothing))))
-        Core.TermVariable v0 -> Optionals.cases (Maps.lookup v0 (Graph.graphPrimitives g)) (encodeVariable env v0 cx g) (\_ ->
+        Core.TermVariable v0 -> Optionals.match (Maps.lookup v0 (Graph.graphPrimitives g)) (encodeVariable env v0 cx g) (\_ ->
           let combinedAnns = Lists.foldl (\acc -> \m -> Maps.union acc m) Maps.empty anns
-          in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mt -> Eithers.bind (Optionals.cases mt (Checking.typeOfTerm cx g term) (\t -> Right t)) (\typ -> case (Strip.deannotateType typ) of
+          in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mt -> Eithers.bind (Optionals.match mt (Checking.typeOfTerm cx g term) (\t -> Right t)) (\typ -> case (Strip.deannotateType typ) of
             Core.TypeFunction v1 -> encodeFunctionPrimitiveByName env (Core.functionTypeDomain v1) (Core.functionTypeCodomain v1) v0 cx g
             _ -> encodeNullaryPrimitiveByName env typ v0 cx g))))
         Core.TermUnit -> Right (Utils.javaLiteralToJavaExpression Syntax.LiteralNull)
@@ -1698,7 +1699,7 @@
               body = Core.typeApplicationTermBody v0
           in (Eithers.bind (encodeType aliases Sets.empty atyp cx g) (\jatyp ->
             let combinedAnns = Lists.foldl (\acc -> \m -> Maps.union acc m) Maps.empty anns
-            in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mtyp -> Eithers.bind (Optionals.cases mtyp (Checking.typeOfTerm cx g term) (\t -> Right t)) (\typ ->
+            in (Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g combinedAnns)) (\mtyp -> Eithers.bind (Optionals.match mtyp (Checking.typeOfTerm cx g term) (\t -> Right t)) (\typ ->
               let collected0 = collectTypeApps0 body [
                     atyp]
                   innermostBody0 = Pairs.first collected0
@@ -1725,8 +1726,8 @@
                   _ -> typeAppFallbackCast env aliases anns tyapps jatyp body correctedTyp cx g)))))))
         _ -> Right (encodeLiteral (Core.LiteralString "Unimplemented term variant"))
 
-encodeTermTCO :: JavaEnvironment.JavaEnvironment -> Core.Name -> [Core.Name] -> M.Map Core.Name Core.Name -> Int -> Core.Term -> Typing.InferenceContext -> Graph.Graph -> Either Errors.Error [Syntax.BlockStatement]
-encodeTermTCO env0 funcName paramNames tcoVarRenames tcoDepth term cx g =
+encodeTermTCO :: JavaEnvironment.JavaEnvironment -> Core.Name -> [Core.Name] -> [Core.Name] -> M.Map Core.Name Core.Name -> Int -> Core.Term -> Typing.InferenceContext -> Graph.Graph -> Either Errors.Error [Syntax.BlockStatement]
+encodeTermTCO env0 funcName paramNames tparams tcoVarRenames tcoDepth term cx g =
 
       let aliases0 = JavaEnvironment.javaEnvironmentAliases env0
           env =
@@ -1777,7 +1778,7 @@
           in (Eithers.bind (bindingsToStatements env letBindings cx g) (\bindResult ->
             let letStmts = Pairs.first bindResult
                 envAfterLet = Pairs.second bindResult
-            in (Eithers.bind (encodeTermTCO envAfterLet funcName paramNames tcoVarRenames tcoDepth letBody cx g) (\tcoBodyStmts -> Right (Lists.concat2 letStmts tcoBodyStmts)))))
+            in (Eithers.bind (encodeTermTCO envAfterLet funcName paramNames tparams tcoVarRenames tcoDepth letBody cx g) (\tcoBodyStmts -> Right (Lists.concat2 letStmts tcoBodyStmts)))))
         _ ->
           let gathered2 = Analysis.gatherApplications term
               args2 = Pairs.first gathered2
@@ -1790,7 +1791,11 @@
                     tname = Core.caseStatementTypeName v0
                     dflt = Core.caseStatementDefault v0
                     cases_ = Core.caseStatementCases v0
-                in (Eithers.bind (domTypeArgs aliases (Resolution.nominalApplication tname []) cx g) (\domArgs -> Eithers.bind (encodeTerm env arg cx g) (\jArgRaw ->
+                    tnameIsGeneric =
+                            Optionals.match (Maps.lookup tname (Graph.graphSchemaTypes g)) False (\ts -> Logic.not (Lists.null (Core.typeSchemeVariables ts)))
+                    domArgs =
+                            Logic.ifElse tnameIsGeneric (Lists.map (\tp -> Utils.typeParameterToTypeArgument (Utils.javaTypeParameter (Formatting.capitalize (Core.unName tp)))) tparams) []
+                in (Eithers.bind (encodeTerm env arg cx g) (\jArgRaw ->
                   let depthSuffix = Logic.ifElse (Equality.equal tcoDepth 0) "" (Literals.showInt32 tcoDepth)
                       matchVarId =
                               Utils.javaIdentifier (Strings.concat [
@@ -1803,6 +1808,7 @@
                     let fieldName = Core.caseAlternativeName field
                         variantRefType =
                                 Utils.nameToJavaReferenceType aliases True domArgs tname (Just (Formatting.capitalize (Core.unName fieldName)))
+                        variantRawType = Utils.nameToJavaReferenceType aliases True [] tname (Just (Formatting.capitalize (Core.unName fieldName)))
                     in case (Strip.deannotateTerm (Core.caseAlternativeHandler field)) of
                       Core.TermLambda v1 -> withLambda env v1 (\env2 ->
                         let lambdaParam = Core.lambdaParameter v1
@@ -1813,7 +1819,7 @@
                                     Utils.javaCastExpressionToJavaExpression (Utils.javaCastExpression variantRefType (Utils.javaExpressionToJavaUnaryExpression jArg))
                             localDecl = Utils.varDeclarationStatement varId castExpr
                             isBranchTailCall = Analysis.isTailRecursiveInTailPosition funcName branchBody
-                        in (Eithers.bind (Logic.ifElse isBranchTailCall (encodeTermTCO env3 funcName paramNames tcoVarRenames (Math.add tcoDepth 1) branchBody cx g) (Eithers.bind (analyzeJavaFunction env3 branchBody cx g) (\fs ->
+                        in (Eithers.bind (Logic.ifElse isBranchTailCall (encodeTermTCO env3 funcName paramNames tparams tcoVarRenames (Math.add tcoDepth 1) branchBody cx g) (Eithers.bind (analyzeJavaFunction env3 branchBody cx g) (\fs ->
                           let bindings = Typing.functionStructureBindings fs
                               innerBody = Typing.functionStructureBody fs
                               env4 = Typing.functionStructureEnvironment fs
@@ -1825,20 +1831,22 @@
                               in (Right (Lists.concat2 bindingStmts [
                                 returnStmt]))))))))) (\bodyStmts ->
                           let relExpr =
-                                  Utils.javaInstanceOf (Utils.javaUnaryExpressionToJavaRelationalExpression (Utils.javaExpressionToJavaUnaryExpression jArg)) variantRefType
+                                  Utils.javaInstanceOf (Utils.javaUnaryExpressionToJavaRelationalExpression (Utils.javaExpressionToJavaUnaryExpression jArg)) variantRawType
                               condExpr = Utils.javaRelationalExpressionToJavaExpression relExpr
                               blockStmts = Lists.cons localDecl bodyStmts
                               ifBody = Syntax.StatementWithoutTrailing (Syntax.StatementWithoutTrailingSubstatementBlock (Syntax.Block blockStmts))
                           in (Right (Syntax.BlockStatementStatement (Syntax.StatementIfThen (Syntax.IfThenStatement {
                             Syntax.ifThenStatementExpression = condExpr,
                             Syntax.ifThenStatementStatement = ifBody})))))))
-                      _ -> Left (Errors.ErrorOther (Errors.OtherError "TCO: case branch is not a lambda"))) cases_) (\ifBlocks -> Eithers.bind (Optionals.cases dflt (Right [
-                    Syntax.BlockStatementStatement (Utils.javaReturnStatement (Just jArg))]) (\d -> Eithers.bind (encodeTerm env d cx g) (\dExpr -> Right [
+                      _ -> Left (Errors.ErrorOther (Errors.OtherError "TCO: case branch is not a lambda"))) cases_) (\ifBlocks -> Eithers.bind (Optionals.match dflt (Right [
+                    Syntax.BlockStatementStatement (Utils.javaThrowIllegalStateException [
+                      Utils.javaAdditiveExpressionToJavaExpression (Utils.addExpressions [
+                        Utils.javaStringMultiplicativeExpression "Non-exhaustive patterns when matching"])])]) (\d -> Eithers.bind (encodeTerm env d cx g) (\dExpr -> Right [
                     Syntax.BlockStatementStatement (Utils.javaReturnStatement (Just dExpr))]))) (\defaultStmt -> Right (Lists.concat [
                     [
                       matchDecl],
                     ifBlocks,
-                    defaultStmt])))))))
+                    defaultStmt]))))))
               _ -> Eithers.bind (encodeTerm env term cx g) (\expr -> Right [
                 Syntax.BlockStatementStatement (Utils.javaReturnStatement (Just expr))])) (Eithers.bind (encodeTerm env term cx g) (\expr -> Right [
             Syntax.BlockStatementStatement (Utils.javaReturnStatement (Just expr))])))))
@@ -1876,7 +1884,7 @@
         Core.TypeUnion _ -> Left (Errors.ErrorOther (Errors.OtherError "unexpected anonymous union type"))
         Core.TypeVariable v0 ->
           let name = Optionals.withDefault v0 (Maps.lookup v0 typeVarSubst)
-          in (Eithers.bind (encodeType_resolveIfTypedef aliases boundVars inScopeTypeParams name cx g) (\resolved -> Optionals.cases resolved (Right (Logic.ifElse (Logic.or (Sets.member name boundVars) (Sets.member name inScopeTypeParams)) (Syntax.TypeReference (Utils.javaTypeVariable (Core.unName name))) (Logic.ifElse (isLambdaBoundVariable name) (Syntax.TypeReference (Utils.javaTypeVariable (Core.unName name))) (Logic.ifElse (isUnresolvedInferenceVar name) (Syntax.TypeReference (Syntax.ReferenceTypeClassOrInterface (Syntax.ClassOrInterfaceTypeClass (Utils.javaClassType [] JavaNames.javaLangPackageName "Object")))) (Syntax.TypeReference (Utils.nameToJavaReferenceType aliases True [] name Nothing)))))) (\resolvedType -> encodeType aliases boundVars resolvedType cx g)))
+          in (Eithers.bind (encodeType_resolveIfTypedef aliases boundVars inScopeTypeParams name cx g) (\resolved -> Optionals.match resolved (Right (Logic.ifElse (Logic.or (Sets.member name boundVars) (Sets.member name inScopeTypeParams)) (Syntax.TypeReference (Utils.javaTypeVariable (Core.unName name))) (Logic.ifElse (isLambdaBoundVariable name) (Syntax.TypeReference (Utils.javaTypeVariable (Core.unName name))) (Logic.ifElse (isUnresolvedInferenceVar name) (Syntax.TypeReference (Syntax.ReferenceTypeClassOrInterface (Syntax.ClassOrInterfaceTypeClass (Utils.javaClassType [] JavaNames.javaLangPackageName "Object")))) (Syntax.TypeReference (Utils.nameToJavaReferenceType aliases True [] name Nothing)))))) (\resolvedType -> encodeType aliases boundVars resolvedType cx g)))
         Core.TypeWrap _ -> Left (Errors.ErrorOther (Errors.OtherError "unexpected anonymous wrap type"))
         _ -> Left (Errors.ErrorOther (Errors.OtherError (Strings.concat2 "can't encode unsupported type in Java: " (PrintCore.type_ t))))
 
@@ -1906,7 +1914,7 @@
 encodeType_resolveIfTypedef aliases boundVars inScopeTypeParams name cx g =
     Logic.ifElse (Logic.or (Sets.member name boundVars) (Sets.member name inScopeTypeParams)) (Right Nothing) (Logic.ifElse (isLambdaBoundVariable name) (Right Nothing) (
       let schemaTypes = Graph.graphSchemaTypes g
-      in (Optionals.cases (Maps.lookup name schemaTypes) (Right Nothing) (\ts -> Logic.ifElse (Logic.not (Lists.null (Core.typeSchemeVariables ts))) (Right Nothing) (case (Strip.deannotateType (Core.typeSchemeBody ts)) of
+      in (Optionals.match (Maps.lookup name schemaTypes) (Right Nothing) (\ts -> Logic.ifElse (Logic.not (Lists.null (Core.typeSchemeVariables ts))) (Right Nothing) (case (Strip.deannotateType (Core.typeSchemeBody ts)) of
         Core.TypeRecord _ -> Right Nothing
         Core.TypeUnion _ -> Right Nothing
         Core.TypeWrap _ -> Right Nothing
@@ -1948,12 +1956,12 @@
 encodeVariable_hoistedLambdaCase :: JavaEnvironment.Aliases -> Core.Name -> Int -> t0 -> Graph.Graph -> Either Errors.Error Syntax.Expression
 encodeVariable_hoistedLambdaCase aliases name arity cx g =
 
-      let paramNames = Lists.map (\i -> Core.Name (Strings.concat2 "p" (Literals.showInt32 i))) (Math.range 0 (Math.sub arity 1))
+      let paramNames = Lists.map (\i -> Core.Name (Strings.concat2 "p" (Literals.showInt32 i))) (Math.range 0 arity)
           paramExprs = Lists.map (\pn -> Utils.javaIdentifierToJavaExpression (Utils.variableToJavaIdentifier pn)) paramNames
           call =
                   Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocation Nothing (elementJavaIdentifier False False aliases name) paramExprs)
           lam = encodeVariable_buildCurried paramNames call
-      in (Eithers.bind (Right (Lexical.lookupBinding g name)) (\mel -> Optionals.cases mel (Right lam) (\el -> Optionals.cases (Core.bindingTypeScheme el) (Right lam) (\ts ->
+      in (Eithers.bind (Right (Lexical.lookupBinding g name)) (\mel -> Optionals.match mel (Right lam) (\el -> Optionals.match (Core.bindingTypeScheme el) (Right lam) (\ts ->
         let typ = Core.typeSchemeBody ts
         in (Eithers.bind (encodeType aliases Sets.empty typ cx g) (\jtype -> Eithers.bind (Utils.javaTypeToJavaReferenceType jtype cx) (\rt -> Right (Utils.javaCastExpressionToJavaExpression (Utils.javaCastExpression rt (Utils.javaExpressionToJavaUnaryExpression lam))))))))))
 
@@ -2041,6 +2049,18 @@
       Core.TypeApplication v0 -> Lists.cons (Core.applicationTypeArgument v0) (extractTypeApplicationArgs_go (Core.applicationTypeFunction v0))
       _ -> []
 
+fieldToStringPart :: Core.FieldType -> [Syntax.MultiplicativeExpression]
+fieldToStringPart f =
+
+      let fname = Core.unName (Core.fieldTypeName f)
+          nameLiteral = Utils.javaStringMultiplicativeExpression (Strings.concat2 fname "=")
+          valueExpr =
+                  Syntax.MultiplicativeExpressionUnary (Utils.javaExpressionToJavaUnaryExpression (Utils.javaExpressionNameToJavaExpression (Utils.fieldExpression (Syntax.Identifier "this") (Utils.javaIdentifier fname))))
+      in [
+        Utils.javaStringMultiplicativeExpression ", ",
+        nameLiteral,
+        valueExpr]
+
 fieldTypeToFormalParam :: JavaEnvironment.Aliases -> Core.FieldType -> t0 -> Graph.Graph -> Either Errors.Error Syntax.FormalParameter
 fieldTypeToFormalParam aliases ft cx g =
     Eithers.bind (encodeType aliases Sets.empty (Core.fieldTypeType ft) cx g) (\jt -> Right (Utils.javaTypeToJavaFormalParameter jt (Core.fieldTypeName ft)))
@@ -2050,7 +2070,7 @@
 
 filterPhantomTypeArgs :: Core.Name -> [Core.Type] -> t0 -> Graph.Graph -> Either t1 [Core.Type]
 filterPhantomTypeArgs calleeName allTypeArgs cx g =
-    Eithers.bind (Right (Lexical.lookupBinding g calleeName)) (\mel -> Optionals.cases mel (Right allTypeArgs) (\el -> Optionals.cases (Core.bindingTypeScheme el) (Right allTypeArgs) (\ts ->
+    Eithers.bind (Right (Lexical.lookupBinding g calleeName)) (\mel -> Optionals.match mel (Right allTypeArgs) (\el -> Optionals.match (Core.bindingTypeScheme el) (Right allTypeArgs) (\ts ->
       let schemeVars = Lists.filter (\v -> isSimpleName v) (Core.typeSchemeVariables ts)
           schemeTypeVars = collectTypeVars (Core.typeSchemeBody ts)
           schemeType = Core.typeSchemeBody ts
@@ -2147,7 +2167,7 @@
             mMethodOverride = Pairs.second wrapResult
         in (Logic.ifElse (Logic.or (isLocalVariable name) isLambdaBound) (Eithers.bind (encodeVariable env name cx g) (\baseExpr -> Right (Lists.foldl (\acc -> \jarg -> applyJavaArg acc jarg) baseExpr jargs))) (
           let overrideMethodName =
-                  \jid -> Optionals.cases mMethodOverride jid (\m ->
+                  \jid -> Optionals.match mMethodOverride jid (\m ->
                     let s = Syntax.unIdentifier jid
                     in (Syntax.Identifier (Strings.concat2 (Strings.fromList (Lists.take (Math.sub (Strings.length s) (Strings.length JavaNames.applyMethodName)) (Strings.toList s))) m)))
           in (Logic.ifElse (Lists.null typeApps) (
@@ -2159,7 +2179,7 @@
             let qn = Names.qualifyName name
                 mns = Util.qualifiedNameModuleName qn
                 localName = Util.qualifiedNameLocal qn
-            in (Optionals.cases mns (
+            in (Optionals.match mns (
               let header =
                       Syntax.MethodInvocation_HeaderSimple (Syntax.MethodName (overrideMethodName (elementJavaIdentifier isPrim False aliases name)))
               in (Right (Utils.javaMethodInvocationToJavaExpression (Syntax.MethodInvocation {
@@ -2177,7 +2197,7 @@
 
 getFunctionType :: M.Map Core.Name Core.Term -> t0 -> Graph.Graph -> Either Errors.Error Core.FunctionType
 getFunctionType ann cx g =
-    Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g ann)) (\mt -> Optionals.cases mt (Left (Errors.ErrorOther (Errors.OtherError "type annotation is required for function and elimination terms in Java"))) (\t -> case t of
+    Eithers.bind (Eithers.bimap (\_de -> Errors.ErrorOther (Errors.OtherError (Errors.unDecodingError _de))) (\_a -> _a) (Annotations.getType g ann)) (\mt -> Optionals.match mt (Left (Errors.ErrorOther (Errors.OtherError "type annotation is required for function and elimination terms in Java"))) (\t -> case t of
       Core.TypeFunction v0 -> Right v0
       _ -> Left (Errors.ErrorOther (Errors.OtherError (Strings.concat2 "expected function type, got: " (PrintCore.type_ t))))))
 
@@ -2186,7 +2206,7 @@
     Lists.foldl (\m -> \p ->
       let k = Pairs.first p
           v = Pairs.second p
-      in (Maps.alter (\mv -> Optionals.cases mv (Just [
+      in (Maps.alter (\mv -> Optionals.match mv (Just [
         v]) (\vs -> Just (Lists.concat2 vs [
         v]))) k m)) Maps.empty pairs
 
@@ -2328,8 +2348,8 @@
 isFieldUnitType typeName fieldName cx g =
 
       let schemaTypes = Graph.graphSchemaTypes g
-      in (Optionals.cases (Maps.lookup typeName schemaTypes) (Right False) (\ts -> case (Strip.deannotateType (Core.typeSchemeBody ts)) of
-        Core.TypeUnion v0 -> Right (Optionals.cases (Lists.find (\ft -> Equality.equal (Core.fieldTypeName ft) fieldName) v0) False (\ft -> Predicates.isUnitType (Strip.deannotateType (Core.fieldTypeType ft))))
+      in (Optionals.match (Maps.lookup typeName schemaTypes) (Right False) (\ts -> case (Strip.deannotateType (Core.typeSchemeBody ts)) of
+        Core.TypeUnion v0 -> Right (Optionals.match (Lists.find (\ft -> Equality.equal (Core.fieldTypeName ft) fieldName) v0) False (\ft -> Predicates.isUnitType (Strip.deannotateType (Core.fieldTypeType ft))))
         _ -> Right False))
 
 isLambdaBoundIn :: Core.Name -> S.Set Core.Name -> Bool
@@ -2436,7 +2456,7 @@
 
 lazyFlagsForPrimitive :: Graph.Graph -> Core.Name -> [Bool]
 lazyFlagsForPrimitive g name =
-    Optionals.cases (Maps.lookup name (Graph.graphPrimitives g)) [] (\prim -> Lists.map (\p -> Typing.parameterIsLazy p) (Typing.termSignatureParameters (Packaging.primitiveDefinitionSignature (Graph.primitiveDefinition prim))))
+    Optionals.match (Maps.lookup name (Graph.graphPrimitives g)) [] (\prim -> Lists.map (\p -> Typing.parameterIsLazy p) (Typing.termSignatureParameters (Packaging.primitiveDefinitionSignature (Graph.primitiveDefinition prim))))
 
 moduleToJava :: S.Set String -> Packaging.Module -> [Packaging.Definition] -> Typing.InferenceContext -> Graph.Graph -> Either Errors.Error (M.Map String String)
 moduleToJava overlaySubs mod defs cx g =
@@ -2526,7 +2546,7 @@
             _ -> annotated
         Core.TermLet v0 ->
           let propagatedBindings =
-                  Lists.map (\b -> Optionals.cases (Core.bindingTypeScheme b) b (\ts -> Core.Binding {
+                  Lists.map (\b -> Optionals.match (Core.bindingTypeScheme b) b (\ts -> Core.Binding {
                     Core.bindingName = (Core.bindingName b),
                     Core.bindingTerm = (propagateType (Core.typeSchemeBody ts) (Core.bindingTerm b)),
                     Core.bindingTypeScheme = (Core.bindingTypeScheme b)})) (Core.letBindings v0)
@@ -2752,6 +2772,25 @@
           ftype = Core.fieldTypeType ft
       in (Eithers.bind (encodeType aliases Sets.empty ftype cx g) (\jt -> Right (Utils.javaMemberField mods jt (Utils.fieldNameToJavaVariableDeclarator fname))))
 
+recordToStringMethod :: Core.Name -> [Core.FieldType] -> Syntax.ClassBodyDeclaration
+recordToStringMethod elName fields =
+
+      let anns = [
+            Utils.overrideAnnotation]
+          mods = [
+                Syntax.MethodModifierPublic]
+          result = Utils.javaTypeToJavaResult (Utils.javaRefType [] Nothing "String")
+          className = Core.unName elName
+          fieldParts = Lists.drop 1 (Lists.concat (Lists.map fieldToStringPart fields))
+          allParts =
+                  Lists.concat2 [
+                    Utils.javaStringMultiplicativeExpression (Strings.concat2 className "{")] (Lists.concat2 fieldParts [
+                    Utils.javaStringMultiplicativeExpression "}"])
+          returnStr =
+                  Syntax.BlockStatementStatement (Utils.javaReturnStatement (Just (Utils.javaAdditiveExpressionToJavaExpression (Utils.addExpressions allParts))))
+      in (Utils.methodDeclaration mods [] anns JavaNames.toStringMethodName [] result (Just [
+        returnStr]))
+
 recordWithMethod :: JavaEnvironment.Aliases -> Core.Name -> [Core.FieldType] -> Core.FieldType -> t0 -> Graph.Graph -> Either Errors.Error Syntax.ClassBodyDeclaration
 recordWithMethod aliases elName fields field cx g =
 
@@ -2812,7 +2851,7 @@
 
       let vid = Syntax.variableDeclaratorId vd
           mInit = Syntax.variableDeclaratorInitializer vd
-      in (Optionals.cases mInit [
+      in (Optionals.match mInit [
         Syntax.InterfaceMemberDeclarationConstant (Syntax.ConstantDeclaration {
           Syntax.constantDeclarationModifiers = mods,
           Syntax.constantDeclarationType = utype,
@@ -2859,7 +2898,7 @@
 substituteTypeVarsWithTypes_go :: M.Map Core.Name Core.Type -> Core.Type -> Core.Type
 substituteTypeVarsWithTypes_go subst t =
     case (Strip.deannotateType t) of
-      Core.TypeVariable v0 -> Optionals.cases (Maps.lookup v0 subst) t (\rep -> rep)
+      Core.TypeVariable v0 -> Optionals.match (Maps.lookup v0 subst) t (\rep -> rep)
       Core.TypeFunction v0 -> Core.TypeFunction (Core.FunctionType {
         Core.functionTypeDomain = (substituteTypeVarsWithTypes_go subst (Core.functionTypeDomain v0)),
         Core.functionTypeCodomain = (substituteTypeVarsWithTypes_go subst (Core.functionTypeCodomain v0))})
@@ -2964,7 +3003,7 @@
                 Core.bindingTerm = Core.TermUnit,
                 Core.bindingTypeScheme = Nothing}) (Lists.head (Lists.filter (\b -> Equality.equal (Core.bindingName b) name) flatBindings))
           value = Core.bindingTerm binding
-      in (Eithers.bind (Optionals.cases (Core.bindingTypeScheme binding) (Checking.typeOfTerm cx gExt value) (\ts -> Right (Core.typeSchemeBody ts))) (\typ -> Eithers.bind (encodeType aliasesExt Sets.empty typ cx g) (\jtype ->
+      in (Eithers.bind (Optionals.match (Core.bindingTypeScheme binding) (Checking.typeOfTerm cx gExt value) (\ts -> Right (Core.typeSchemeBody ts))) (\typ -> Eithers.bind (encodeType aliasesExt Sets.empty typ cx g) (\jtype ->
         let id = Utils.variableToJavaIdentifier name
             arid = Syntax.Identifier "java.util.concurrent.atomic.AtomicReference"
             aid =
@@ -2999,7 +3038,7 @@
                 Core.bindingTerm = Core.TermUnit,
                 Core.bindingTypeScheme = Nothing}) (Lists.head (Lists.filter (\b -> Equality.equal (Core.bindingName b) name) flatBindings))
           value = Core.bindingTerm binding
-      in (Eithers.bind (Optionals.cases (Core.bindingTypeScheme binding) (Checking.typeOfTerm cx gExt value) (\ts -> Right (Core.typeSchemeBody ts))) (\typ -> Eithers.bind (encodeType aliasesExt Sets.empty typ cx g) (\jtype ->
+      in (Eithers.bind (Optionals.match (Core.bindingTypeScheme binding) (Checking.typeOfTerm cx gExt value) (\ts -> Right (Core.typeSchemeBody ts))) (\typ -> Eithers.bind (encodeType aliasesExt Sets.empty typ cx g) (\jtype ->
         let id = Utils.variableToJavaIdentifier name
             annotatedValue = Annotations.setTermAnnotation Constants.keyType (Just (EncodeCore.type_ typ)) value
         in (Eithers.bind (encodeTerm envExt annotatedValue cx g) (\rhs -> Logic.ifElse (Sets.member name recursiveVars) (Right (Syntax.BlockStatementStatement (Utils.javaMethodInvocationToJavaStatement (Utils.methodInvocation (Just (Left (Syntax.ExpressionName {
@@ -3047,15 +3086,15 @@
           mns = Util.qualifiedNameModuleName qn
           localName = Util.qualifiedNameLocal qn
       in case cls of
-        JavaEnvironment.JavaSymbolClassNullaryFunction -> Optionals.cases mns (typeAppFallbackCast env aliases anns tyapps jatyp body correctedTyp cx g) (\ns_ ->
+        JavaEnvironment.JavaSymbolClassNullaryFunction -> Optionals.match mns (typeAppFallbackCast env aliases anns tyapps jatyp body correctedTyp cx g) (\ns_ ->
           let classId = Utils.nameToJavaName aliases (elementsQualifiedName ns_)
               methodId = Syntax.Identifier (Utils.sanitizeJavaName localName)
           in (Eithers.bind (filterPhantomTypeArgs varName allTypeArgs cx g) (\filteredTypeArgs -> Eithers.bind (Eithers.mapList (\t -> Eithers.bind (encodeType aliases Sets.empty t cx g) (\jt -> Eithers.bind (Utils.javaTypeToJavaReferenceType jt cx) (\rt -> Right (Syntax.TypeArgumentReference rt)))) filteredTypeArgs) (\jTypeArgs -> Right (Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStaticWithTypeArgs classId methodId jTypeArgs []))))))
-        JavaEnvironment.JavaSymbolClassHoistedLambda v0 -> Optionals.cases mns (typeAppFallbackCast env aliases anns tyapps jatyp body correctedTyp cx g) (\ns_ ->
+        JavaEnvironment.JavaSymbolClassHoistedLambda v0 -> Optionals.match mns (typeAppFallbackCast env aliases anns tyapps jatyp body correctedTyp cx g) (\ns_ ->
           let classId = Utils.nameToJavaName aliases (elementsQualifiedName ns_)
               methodId = Syntax.Identifier (Utils.sanitizeJavaName localName)
           in (Eithers.bind (filterPhantomTypeArgs varName allTypeArgs cx g) (\filteredTypeArgs -> Eithers.bind (Eithers.mapList (\t -> Eithers.bind (encodeType aliases Sets.empty t cx g) (\jt -> Eithers.bind (Utils.javaTypeToJavaReferenceType jt cx) (\rt -> Right (Syntax.TypeArgumentReference rt)))) filteredTypeArgs) (\jTypeArgs ->
-            let paramNames = Lists.map (\i -> Core.Name (Strings.concat2 "p" (Literals.showInt32 i))) (Math.range 0 (Math.sub v0 1))
+            let paramNames = Lists.map (\i -> Core.Name (Strings.concat2 "p" (Literals.showInt32 i))) (Math.range 0 v0)
                 paramExprs = Lists.map (\p -> Utils.javaIdentifierToJavaExpression (Utils.variableToJavaIdentifier p)) paramNames
                 call =
                         Utils.javaMethodInvocationToJavaExpression (Utils.methodInvocationStaticWithTypeArgs classId methodId jTypeArgs paramExprs)
diff --git a/src/main/haskell/Hydra/Java/Environment.hs b/src/main/haskell/Hydra/Java/Environment.hs
--- a/src/main/haskell/Hydra/Java/Environment.hs
+++ b/src/main/haskell/Hydra/Java/Environment.hs
@@ -9,7 +9,7 @@
 import qualified Hydra.Java.Syntax as Syntax
 import qualified Hydra.Packaging as Packaging
 import qualified Hydra.Typing as Typing
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 import qualified Data.Map as M
 import qualified Data.Set as S
diff --git a/src/main/haskell/Hydra/Java/Language.hs b/src/main/haskell/Hydra/Java/Language.hs
--- a/src/main/haskell/Hydra/Java/Language.hs
+++ b/src/main/haskell/Hydra/Java/Language.hs
@@ -31,7 +31,7 @@
 import qualified Hydra.Util as Util
 import qualified Hydra.Validation as Validation
 import qualified Hydra.Variants as Variants
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 import qualified Data.Set as S
 
diff --git a/src/main/haskell/Hydra/Java/Names.hs b/src/main/haskell/Hydra/Java/Names.hs
--- a/src/main/haskell/Hydra/Java/Names.hs
+++ b/src/main/haskell/Hydra/Java/Names.hs
@@ -29,7 +29,7 @@
 import qualified Hydra.Util as Util
 import qualified Hydra.Validation as Validation
 import qualified Hydra.Variants as Variants
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 
 acceptMethodName :: String
@@ -110,6 +110,9 @@
 
 setMethodName :: String
 setMethodName = "set"
+
+toStringMethodName :: String
+toStringMethodName = "toString"
 
 valueFieldName :: String
 valueFieldName = "value"
diff --git a/src/main/haskell/Hydra/Java/Serde.hs b/src/main/haskell/Hydra/Java/Serde.hs
--- a/src/main/haskell/Hydra/Java/Serde.hs
+++ b/src/main/haskell/Hydra/Java/Serde.hs
@@ -13,6 +13,7 @@
 import qualified Hydra.Error.Core as ErrorCore
 import qualified Hydra.Error.Packaging as ErrorPackaging
 import qualified Hydra.Errors as Errors
+import qualified Hydra.Formatting as Formatting
 import qualified Hydra.Graph as Graph
 import qualified Hydra.Java.Syntax as Syntax
 import qualified Hydra.Json.Model as Model
@@ -40,7 +41,7 @@
 import qualified Hydra.Util as Util
 import qualified Hydra.Validation as Validation
 import qualified Hydra.Variants as Variants
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 
 additionalBoundToExpr :: Syntax.AdditionalBound -> Ast.Expr
@@ -244,7 +245,7 @@
 
       let mqual = Syntax.classInstanceCreationExpressionQualifier cice
           e = Syntax.classInstanceCreationExpressionExpression cice
-      in (Optionals.cases mqual (unqualifiedClassInstanceCreationExpressionToExpr e) (\q -> Serialization.dotSep [
+      in (Optionals.match mqual (unqualifiedClassInstanceCreationExpressionToExpr e) (\q -> Serialization.dotSep [
         classInstanceCreationExpressionQualifierToExpr q,
         (unqualifiedClassInstanceCreationExpressionToExpr e)]))
 
@@ -1056,7 +1057,7 @@
 
       let tname = Syntax.singleElementAnnotationName sea
           mv = Syntax.singleElementAnnotationValue sea
-      in (Optionals.cases mv (markerAnnotationToExpr (Syntax.MarkerAnnotation tname)) (\v -> Serialization.prefix "@" (Serialization.noSep [
+      in (Optionals.match mv (markerAnnotationToExpr (Syntax.MarkerAnnotation tname)) (\v -> Serialization.prefix "@" (Serialization.noSep [
         typeNameToExpr tname,
         (Serialization.parenList False [
           elementValueToExpr v])])))
@@ -1272,7 +1273,7 @@
       let id = Syntax.variableDeclaratorId vd
           minit = Syntax.variableDeclaratorInitializer vd
           idSec = variableDeclaratorIdToExpr id
-      in (Optionals.cases minit idSec (\init -> Serialization.infixWs "=" idSec (variableInitializerToExpr init)))
+      in (Optionals.match minit idSec (\init -> Serialization.infixWs "=" idSec (variableInitializerToExpr init)))
 
 variableInitializerToExpr :: Syntax.VariableInitializer -> Ast.Expr
 variableInitializerToExpr i =
@@ -1291,7 +1292,7 @@
 
       let mcond = Syntax.whileStatementCond ws
           body = Syntax.whileStatementBody ws
-          condSer = Optionals.cases mcond (Serialization.cst "true") (\c -> expressionToExpr c)
+          condSer = Optionals.match mcond (Serialization.cst "true") (\c -> expressionToExpr c)
       in (Serialization.spaceSep [
         Serialization.cst "while",
         (Serialization.parenList False [
@@ -1321,6 +1322,6 @@
 -- | Wrap an expression with optional Javadoc comments. Blank lines inside the doc body emit ` *` (no trailing space) instead of ` * `.
 withComments :: Maybe String -> Ast.Expr -> Ast.Expr
 withComments mc expr =
-    Optionals.cases mc expr (\c -> Serialization.newlineSep [
-      Serialization.cst (Strings.concat2 "/**\n" (Strings.concat2 (Strings.join "\n" (Lists.map (\l -> Logic.ifElse (Equality.equal l "") " *" (Strings.concat2 " * " l)) (Strings.lines (sanitizeJavaComment (Docs.renderDocStringWith javaDocEntityRef c))))) "\n */")),
+    Optionals.match mc expr (\c -> Serialization.newlineSep [
+      Serialization.cst (Strings.concat2 "/**\n" (Strings.concat2 (Strings.join "\n" (Lists.map (\l -> Logic.ifElse (Equality.equal l "") " *" (Strings.concat2 " * " l)) (Formatting.lines (sanitizeJavaComment (Docs.renderDocStringWith javaDocEntityRef c))))) "\n */")),
       expr])
diff --git a/src/main/haskell/Hydra/Java/Syntax.hs b/src/main/haskell/Hydra/Java/Syntax.hs
--- a/src/main/haskell/Hydra/Java/Syntax.hs
+++ b/src/main/haskell/Hydra/Java/Syntax.hs
@@ -7,7 +7,7 @@
 module Hydra.Java.Syntax where
 
 import qualified Hydra.Core as Core
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 
 newtype Identifier =
diff --git a/src/main/haskell/Hydra/Java/Testing.hs b/src/main/haskell/Hydra/Java/Testing.hs
--- a/src/main/haskell/Hydra/Java/Testing.hs
+++ b/src/main/haskell/Hydra/Java/Testing.hs
@@ -39,7 +39,7 @@
 import qualified Hydra.Util as Util
 import qualified Hydra.Validation as Validation
 import qualified Hydra.Variants as Variants
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 
 -- | Build the complete Java test module content
diff --git a/src/main/haskell/Hydra/Java/Utils.hs b/src/main/haskell/Hydra/Java/Utils.hs
--- a/src/main/haskell/Hydra/Java/Utils.hs
+++ b/src/main/haskell/Hydra/Java/Utils.hs
@@ -46,7 +46,7 @@
 import qualified Hydra.Util as Util
 import qualified Hydra.Validation as Validation
 import qualified Hydra.Variants as Variants
-import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
+import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
 import qualified Data.Scientific as Sci
 import qualified Data.Map as M
 import qualified Data.Set as S
@@ -191,7 +191,7 @@
 javaArrayCreation :: Syntax.PrimitiveTypeWithAnnotations -> Maybe Syntax.ArrayInitializer -> Syntax.Expression
 javaArrayCreation primType minit =
 
-      let init_ = Optionals.cases minit (Syntax.ArrayInitializer []) (\i -> i)
+      let init_ = Optionals.match minit (Syntax.ArrayInitializer []) (\i -> i)
       in (javaPrimaryToJavaExpression (Syntax.PrimaryArrayCreation (Syntax.ArrayCreationExpressionWithInit (Syntax.ArrayCreationExpressionWithInitializerPrimitive (Syntax.ArrayCreationExpressionWithInitializer_Primitive {
         Syntax.arrayCreationExpressionWithInitializer_PrimitiveType = primType,
         Syntax.arrayCreationExpressionWithInitializer_PrimitiveDims = [],
@@ -264,7 +264,7 @@
 javaClassType :: [Syntax.ReferenceType] -> Maybe Syntax.PackageName -> String -> Syntax.ClassType
 javaClassType args pkg id =
 
-      let qual = Optionals.cases pkg Syntax.ClassTypeQualifierNone (\p -> Syntax.ClassTypeQualifierPackage p)
+      let qual = Optionals.match pkg Syntax.ClassTypeQualifierNone (\p -> Syntax.ClassTypeQualifierPackage p)
           targs = Lists.map (\rt -> Syntax.TypeArgumentReference rt) args
       in Syntax.ClassType {
         Syntax.classTypeAnnotations = [],
@@ -500,7 +500,7 @@
 
 javaMethodBody :: Maybe [Syntax.BlockStatement] -> Syntax.MethodBody
 javaMethodBody mstmts =
-    Optionals.cases mstmts Syntax.MethodBodyNone (\stmts -> Syntax.MethodBodyBlock (Syntax.Block stmts))
+    Optionals.match mstmts Syntax.MethodBodyNone (\stmts -> Syntax.MethodBodyBlock (Syntax.Block stmts))
 
 javaMethodDeclarationToJavaClassBodyDeclaration :: Syntax.MethodDeclaration -> Syntax.ClassBodyDeclaration
 javaMethodDeclarationToJavaClassBodyDeclaration md =
@@ -762,7 +762,7 @@
 
 lookupJavaVarName :: Environment.Aliases -> Core.Name -> Core.Name
 lookupJavaVarName aliases name =
-    Optionals.cases (Maps.lookup name (Environment.aliasesVarRenames aliases)) name (\renamed -> renamed)
+    Optionals.match (Maps.lookup name (Environment.aliasesVarRenames aliases)) name (\renamed -> renamed)
 
 makeConstructor :: Environment.Aliases -> Core.Name -> Bool -> [Syntax.FormalParameter] -> [Syntax.BlockStatement] -> Syntax.ClassBodyDeclaration
 makeConstructor aliases elName private params stmts =
@@ -798,7 +798,7 @@
 methodInvocation lhs methodName args =
 
       let header =
-              Optionals.cases lhs (Syntax.MethodInvocation_HeaderSimple (Syntax.MethodName methodName)) (\either -> Syntax.MethodInvocation_HeaderComplex (Syntax.MethodInvocation_Complex {
+              Optionals.match lhs (Syntax.MethodInvocation_HeaderSimple (Syntax.MethodName methodName)) (\either -> Syntax.MethodInvocation_HeaderComplex (Syntax.MethodInvocation_Complex {
                 Syntax.methodInvocation_ComplexVariant = (Eithers.either (\en -> Syntax.MethodInvocation_VariantExpression en) (\p -> Syntax.MethodInvocation_VariantPrimary p) either),
                 Syntax.methodInvocation_ComplexTypeArguments = [],
                 Syntax.methodInvocation_ComplexIdentifier = methodName}))
@@ -840,9 +840,9 @@
       let qn = Names.qualifyName name
           ns_ = Util.qualifiedNameModuleName qn
           local = Util.qualifiedNameLocal qn
-      in (Logic.ifElse (isEscaped (Core.unName name)) (Syntax.Identifier (sanitizeJavaName local)) (Optionals.cases ns_ (Syntax.Identifier local) (\gname ->
+      in (Logic.ifElse (isEscaped (Core.unName name)) (Syntax.Identifier (sanitizeJavaName local)) (Optionals.match ns_ (Syntax.Identifier local) (\gname ->
         let parts =
-                Optionals.cases (Maps.lookup gname (Environment.aliasesPackages aliases)) (Strings.splitOn "." (Packaging.unModuleName gname)) (\pkgName -> Lists.map (\i -> Syntax.unIdentifier i) (Syntax.unPackageName pkgName))
+                Optionals.match (Maps.lookup gname (Environment.aliasesPackages aliases)) (Strings.splitOn "." (Packaging.unModuleName gname)) (\pkgName -> Lists.map (\i -> Syntax.unIdentifier i) (Syntax.unPackageName pkgName))
             allParts = Lists.concat2 parts [
                   sanitizeJavaName local]
         in (Syntax.Identifier (Strings.join "." allParts)))))
@@ -861,11 +861,11 @@
           ns_ = Util.qualifiedNameModuleName qn
           local = Util.qualifiedNameLocal qn
           alias =
-                  Optionals.cases ns_ Nothing (\n -> Just (Optionals.cases (Maps.lookup n (Environment.aliasesPackages aliases)) (JavaNames.javaPackageName (Strings.splitOn "." (Packaging.unModuleName n))) (\id -> id)))
+                  Optionals.match ns_ Nothing (\n -> Just (Optionals.match (Maps.lookup n (Environment.aliasesPackages aliases)) (JavaNames.javaPackageName (Strings.splitOn "." (Packaging.unModuleName n))) (\id -> id)))
           pkg =
-                  Logic.ifElse qualify (Optionals.cases alias Syntax.ClassTypeQualifierNone (\p -> Syntax.ClassTypeQualifierPackage p)) Syntax.ClassTypeQualifierNone
+                  Logic.ifElse qualify (Optionals.match alias Syntax.ClassTypeQualifierNone (\p -> Syntax.ClassTypeQualifierPackage p)) Syntax.ClassTypeQualifierNone
           jid =
-                  javaTypeIdentifier (Optionals.cases mlocal (sanitizeJavaName local) (\l -> Strings.concat2 (Strings.concat2 (sanitizeJavaName local) ".") (sanitizeJavaName l)))
+                  javaTypeIdentifier (Optionals.match mlocal (sanitizeJavaName local) (\l -> Strings.concat2 (Strings.concat2 (sanitizeJavaName local) ".") (sanitizeJavaName l)))
       in (jid, pkg)
 
 overlayJavaLibPackageAliases :: S.Set String -> M.Map Packaging.ModuleName Syntax.PackageName
