The spec (§3b.5) defines Query[C] as a phantom-typed parameterized query type where the compiler auto-extracts {expr} interpolations as bound parameters. During implementation, a fundamental design flaw was discovered: the compiler generates $1, $2 placeholders (PostgreSQL syntax), baking SQL dialect knowledge into a language primitive. User-authored effect handlers cannot participate in parameterization because they receive an opaque const char* at the C level with no access to the decomposed parts.
Cross-language research confirmed a universal pattern: Python 3.14 Template (PEP 750), C# FormattableString, and JS tagged templates all have the language decompose the interpolated string into literal parts + values, and the library/handler reassemble with its own dialect-specific syntax. This deliberation refines the existing Query[C] decision to follow this pattern.
Five panelists (systems, web/scripting, PLT, DevOps/tooling, AI/ML) voted independently on 3 questions. Refines prior decisions: OPEN_QUESTIONS.md 3.1 (Query[C] 3-0) and OPEN_QUESTIONS.md 3.2 (Raw(expr) 2-1).
Q1: Naming — Query[C] vs Template vs Interpolation vs other? (3-1-1 for Template)
- Systems:
Template.Query[C]encodes a domain assumption into a language primitive — a layering violation. The name should reflect the structure, not the use case. Python 3.14, C#, and JS all converged on this after similar debates. - Web:
Template. Every JS developer knows tagged templates. Every Python developer will recognizeTemplatefrom PEP 750.Query[C]will confuse the first developer who uses it for HTML or logging. - PLT: (dissent)
Interpolation. Names the compiler's introduction form, not the use-site context.Templatedrags in template-engine mental models. Will acceptTemplateif overruled. - DevOps:
Template.error[E0310]: expected Template[DB], found Stris immediately legible.Query[Shell]is not self-documenting in error messages. - AI/ML: (dissent) Keep
Query[C]. "Query" carries parameterization semantics in virtually every major framework in training data.Templatehas near-zero training corpus coverage for this usage.
Q2: Internal structure — compiler rewrites to $1/$2 vs decomposed { parts, values } vs hybrid? (3-2 for decomposed)
- Systems: Decomposed. The only option that produces predictable, dialect-free C output. Option A is a layering violation. Option C still ships dialect logic in disguise via helper methods.
- Web: (dissent) Hybrid. Decomposed is correct but brutal for the 90% case. A
to_parameterized(style)helper covers the happy path without forcing every handler author to manually zip parts and values. - PLT: Decomposed. The compiler's job is to preserve structure; the handler's job is to interpret it. This is how Koka-style effect handlers should work — the handler owns the semantics, the language owns the decomposition.
- DevOps: (dissent) Hybrid. Decomposed
parts/valuesis what makes static analysis tractable, but without a helper, every handler author reinvents the wheel. - AI/ML: Decomposed. Matches JS tagged template literals (
strings[],values[]), which have massive training data coverage. Handler authors can emit any format (?,:name,%s).
Q3: Type identity — phantom-only vs single structural vs structural+phantom? (4-1 for structural+phantom)
- Systems: Structural+phantom (
Template[C]). The phantom is load-bearing for safety: without it, aTemplatebuilt for Shell is silently passable to a SQL handler. Phantom is erased at codegen — zero runtime overhead. - Web: Structural+phantom. Phantom-only means handlers can't introspect parts. Pure structural means no cross-context protection. Need both. This is exactly the TypeScript branded types model.
- PLT: Structural+phantom. The coercion rule
InterpString → Template[C]is sound because the compiler synthesizes the phantom at the interpolation site from the surrounding effect context. Variance must be invariant inC. - DevOps: Structural+phantom. The phantom parameter is load-bearing for diagnostics:
expected Template[DB], found Template[Shell]is a clear, actionable error. - AI/ML: (dissent) Single structural type. Phantom types are an invisible abstraction LLMs frequently mishandle. Prefers enforcement via trait bounds or effect constraints. Acknowledges this trades static safety for learnability.
Structural representation:
type Template[C] {
parts: List[Str] // literal segments (parts.len() == values.len() + 1)
values: List[Any] // interpolated values, typed
}
Compiler coercion: When an interpolated string literal appears where Template[C] is expected, the compiler decomposes it into parts and values instead of concatenating. The phantom C is inferred from the surrounding effect context (e.g., db.query() expects Template[DB]).
Handler reassembly: Each handler decides its own parameterization syntax:
// PostgreSQL handler uses $1, $2
// MySQL handler uses ?
// Shell handler uses quoting
// HTML handler uses entity escaping
Unchanged developer syntax: db.query("SELECT * FROM t WHERE id = {id}") — identical to before. The magic is in the type of db.query's parameter.
Raw(expr) unchanged: Wrapping an interpolation in Raw() folds the value into the literal parts (concatenated, not parameterized). Emits W0310 warning. This is unchanged from the prior decision.
Open items for implementation:
- Value representation: typed union vs
void*+tag vs trait-bound dispatch parts.len() == values.len() + 1invariant must be explicit in spec- Phantom inference at ambiguous sites needs fallback error