Introducing QuasiQuoters
We're excited to announce QuasiQuoters (QQ), a powerful new feature in Nic that enables type-safe, compile-time validated domain-specific languages.
The Problem
Embedding domain-specific content in code often requires string manipulation that's error-prone and lacks type safety:
// Traditional approach - no compile-time validation
let query = "SELECT * FROM users WHERE id = " + id_string;
let pattern = "[a-zA-Z]+"; // Hope you got the regex right!These strings are just text. Typos, syntax errors, or type mismatches are only caught at runtime - if at all.
The Solution: QuasiQuoters
QuasiQuoters provide compile-time parsing and validation of domain-specific literals:
// With QuasiQuoters - type-safe and validated at compile time
let query = sql`SELECT * FROM users WHERE id = ${user_id}`;
let pattern = regex`[a-zA-Z]+`;The compiler parses these literals at compile time, validates their syntax, and provides full type safety.
How It Works
A QuasiQuoter is defined as a function that transforms raw string content into a typed value at compile time:
// The type: a function from string to a specific type
type QuasiQuoter[T] = fn(string) -> T;
// Usage: name`content` calls the QuasiQuoter and returns type T
let result: T = name`content`;Built-in QuasiQuoters
Nic provides several built-in QQs:
// Regular expressions
let email_pattern = regex`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`;
// SQL queries (with interpolation)
let user_query = sql`SELECT name, email FROM users WHERE id = ${user_id}`;
// JSON (returns typed data)
let config = json`{"debug": true, "port": 8080}`;Interpolation
QuasiQuoters support variable interpolation with ${expr}:
let name = String.from("Alice");
let age = 30;
let greeting = fmt`Hello, ${name}! You are ${age} years old.`;The interpolated expressions are type-checked and safely converted to the target format.
Creating Custom QuasiQuoters
You can define your own QuasiQuoters for domain-specific needs:
// A simple URI QuasiQuoter
fn uri_qq(raw: string) -> Uri {
// Parse and validate URI at compile time
return Uri.parse(raw);
}
// Register as a QuasiQuoter
qq uri = uri_qq;
// Usage
let api_endpoint = uri`https://api.example.com/v1/users`;Parser Generators
One powerful application is parser generators:
// Define a grammar
let expr_parser = parser`
expr := term (('+' | '-') term)*
term := factor (('*' | '/') factor)*
factor := number | '(' expr ')'
number := [0-9]+
`;
// Use the generated parser
let ast = expr_parser.parse("3 + 4 * 5");The grammar is validated at compile time, and a type-safe parser is generated.
Why QuasiQuoters?
1. Compile-Time Validation
Errors are caught during compilation, not at runtime:
// Compilation error: invalid regex syntax
let bad = regex`[a-z`; // Error: unclosed character class2. Type Safety
The result type is known at compile time:
let query = sql`SELECT name FROM users`;
// query has type SqlQuery, not just string3. Better Tooling
IDEs can provide syntax highlighting, completion, and error checking within QQ literals.
4. Performance
Parsing happens at compile time. The runtime cost is minimal - often just validating pre-computed values.
The Journey Here
QuasiQuoters replace our experimental "temporal zippers" feature. While temporal zippers were an interesting exploration of time-based state management, QuasiQuoters provide more practical, widely-applicable value.
The design draws inspiration from:
- Haskell's Template Haskell QuasiQuotes
- Scala's string interpolation
- Rust's procedural macros
Getting Started
Check out the QuasiQuoters Guide in Learn Nic for complete documentation and examples.
QuasiQuoters are available in Nic v0.3.0 and later.
What's Next?
We're exploring:
- More built-in QQs: HTML, CSS, GraphQL, TOML
- QQ macros: User-defined compile-time transformations
- IDE integration: Syntax highlighting within QQ literals in VS Code
Stay tuned for more updates, and let us know what domain-specific languages you'd like to see supported!
Have questions or feedback? Open an issue on GitHub or join the discussion.