How do I implement a fluent API in EK9?
← Design Patterns and Idioms · Ref: Q265
EK9 supports fluent APIs through two approaches: immutable builder classes where each method returns a new instance, and stream pipelines which are the natural fluent pattern.
IMMUTABLE BUILDER PATTERN
Each method returns a new builder instance with accumulated state:
QueryBuilder
query as String
select()
-> columns as String
<- rtn as QueryBuilder: QueryBuilder("SELECT " + columns)
from()
-> table as String
<- rtn as QueryBuilder: QueryBuilder(query + " FROM " + table)
This is immutable: each call creates a new builder rather than mutating the current one.
CHAINING CALLS
Store intermediate results and chain:
qb <- QueryBuilder() qb: qb.select("name, age") qb: qb.from("users") qb: qb.where("age > 18") stdout.println(qb.build())
STREAM PIPELINES AS FLUENT API
EK9's stream pipelines are the idiomatic fluent pattern:
cat numbers | filter by isEven | map with doubleIt | collect as List of Integer
This is naturally fluent: each stage passes results to the next.
WHY NOT RETURN THIS
In many languages, fluent APIs work by returning 'this' from each method. EK9 encourages immutability: methods return new instances rather than mutating and returning the same object. This makes each intermediate state independent and safe to reuse.
WHEN TO USE EACH
Use immutable builders for configuration and query construction. Use stream pipelines for data transformation. Use function composition (Q59) for processing chains.
See Q89 for list streams. See Q235 for stream operations reference. See Q237 for streams vs loops.
Example
defines module qa.patterns.fluent defines function isEven() as pure -> num as Integer <- rtn as Boolean: num mod 2 == 0 doubleIt() as pure -> num as Integer <- rtn as Integer: num * 2 defines class QueryBuilder query as String: String() default QueryBuilder() QueryBuilder() -> q as String this.query :=? String(q) select() -> columns as String <- rtn as QueryBuilder: QueryBuilder("SELECT " + columns) from() -> table as String <- rtn as QueryBuilder: QueryBuilder(`${query} FROM ${table}`) where() -> condition as String <- rtn as QueryBuilder: QueryBuilder(`${query} WHERE ${condition}`) build() as pure <- rtn as String: String(query) default operator ? defines program FluentApiDemo() stdout <- Stdout() // === IMMUTABLE BUILDER === qb <- QueryBuilder() qb: qb.select("name, age") qb: qb.from("users") qb: qb.where("age > 18") stdout.println(qb.build()) // === REUSABLE BASE === base <- QueryBuilder().select("id, name") fromUsers <- base.from("users") fromOrders <- base.from("orders") stdout.println(fromUsers.build()) stdout.println(fromOrders.build()) // === STREAM PIPELINE: NATURAL FLUENT === numbers <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] evens <- cat numbers | filter by isEven | map with doubleIt | collect as List of Integer stdout.println(`Doubled evens: ${evens}`)
Common mistakes
E08180 — Class fields must be initialised inline. Declaring a field without an initialiser triggers E08180 — uninitialised field. Always provide a default value. See ek9 -h E08180 for details.
Incorrect:
query as String
Correct:
query as String: String()
E50001 — Renaming the variable means later references to 'qb' become unresolved, triggering E50001. Variable names must be consistent throughout the scope. See ek9 -h E50001 for details.
Incorrect:
qbXYZ <- QueryBuilder()
Correct:
qb <- QueryBuilder()
Other ways to ask this
- How do I chain method calls in EK9?
- How do I create a builder with method chaining?
- What is the EK9 approach to fluent interfaces?
Coming from another language?
Java: fluent APIs return this, StringBuilder pattern, Stream API for data. Python: method chaining returns self, list comprehensions. Rust: builder pattern with consume-and-return, iterator chains. Go: functional options pattern, no method chaining convention. Kotlin: apply/also scope functions, Sequence for lazy chains. EK9: immutable builder (each method returns new instance), stream pipelines (cat | filter | map | collect) as natural fluent API.
Keywords: builder, immutable, design, api, compose, method, stream, idiom, fluent, pipeline, chain, pattern