How do I implement the builder pattern in EK9?
← Classes and OOP · Ref: Q118
EK9 supports builder-like patterns through record merge operations and multiple constructors. The merge operator ':~:' is particularly useful for incremental object construction.
BUILDER IN EK9
Use a record with the merge operator to build up an object:
config <- ServerConfig() config :~: partial1 config :~: partial2
Each merge adds only the SET fields from the source.
FLUENT API PATTERN
Define builder methods that return the modified object:
withHost()
-> host as String
<- rtn as ServerConfig: ServerConfig()
rtn :=: this
rtn.host: host
USING RECORDS AS BUILDERS
Records with public fields and merge operator serve as natural builders. Create partial records and merge them together.
ALTERNATIVE NAMED CONSTRUCTOR PARAMETERS
EK9 supports named parameters in constructors:
config <- ServerConfig(host: "localhost", port: 8080)
This can replace simple builders.
See Q94 for constructors. See Q97 for records. See Q98 for record operators.
Example
defines module qa.oop.builder defines record ServerConfig host <- String() port <- Integer() maxConnections <- Integer() ServerConfig() -> host as String port as Integer maxConnections as Integer this.host: host this.port: port this.maxConnections: maxConnections operator :=: -> from as ServerConfig host :=: from.host port :=: from.port maxConnections :=: from.maxConnections operator :~: -> from as ServerConfig if from.host? host :=: from.host if from.port? port :=: from.port if from.maxConnections? maxConnections :=: from.maxConnections operator $ as pure <- rtn as String: `${host}:${port} (max:${maxConnections})` default operator ? defines program BuilderDemo() stdout <- Stdout() // === NAMED CONSTRUCTOR PARAMETERS === config1 <- ServerConfig("localhost", 8080, 100) stdout.println(`Direct: ${config1}`) // === BUILD WITH MERGE === config2 <- ServerConfig() partial1 <- ServerConfig() partial1.host: "production.example.com" config2 :~: partial1 partial2 <- ServerConfig() partial2.port: 443 partial2.maxConnections: 500 config2 :~: partial2 stdout.println(`Merged: ${config2}`) // === COPY AND MODIFY === config3 <- ServerConfig() config3 :=: config1 config3.port: 9090 stdout.println(`Modified copy: ${config3}`)
Common mistakes
E06180 — Class fields are private and cannot be accessed externally. Changing the record to a class makes 'partial1.host: ...' trigger E06180 — not accessible from this context. Use records for builder patterns because record fields are public. See ek9 -h E06180 for details.
Incorrect:
defines class
Correct:
defines record
E50060 — The replace operator ':^:' is not defined on ServerConfig. Using ':^:' instead of ':~:' triggers E50060 — method/function not resolved. Define the operator or use the correct one. See ek9 -h E50060 for details.
Incorrect:
config2 :^: partial2
Correct:
config2 :~: partial2
E50060 — The replace operator ':^:' is not defined on ServerConfig. Using ':^:' instead of ':=:' triggers E50060 — method/function not resolved. Define the operator or use the correct one. See ek9 -h E50060 for details.
Incorrect:
config3 :^: config1
Correct:
config3 :=: config1
E50001 — EK9 does not have a Java-style builder with method chaining. Use constructors for direct creation or the merge operator ':~:' for incremental building. See ek9 -h E50001 for details.
Incorrect:
config1 <- ServerConfig.builder().host("localhost").port(8080).build()
Correct:
config1 <- ServerConfig("localhost", 8080, 100)
E07430 — Mutation operators (:~:, :=:, :^:, +=, -=) mutate the object in place and must not have return values. Adding a return declaration triggers E07430. These operators modify 'this' directly. See ek9 -h E07430 for details.
Incorrect:
operator :~: -> from as ServerConfig <- rtn as ServerConfig: ServerConfig()
Correct:
operator :~: -> from as ServerConfig
E50060 — EK9 does not have a toString() method. Use the $ operator or string interpolation. The record's operator $ handles string conversion. See ek9 -h E50060 for details.
Incorrect:
stdout.println(config1.toString())
Correct:
stdout.println(`Direct: ${config1}`)
Other ways to ask this
- How do I build complex objects step by step in EK9?
- Does EK9 support fluent builder APIs?
- How do I use record merge for building objects?
Coming from another language?
Java: Builder pattern with nested static class, method chaining, .build() call. Lombok @Builder for auto-generation. Python: kwargs in constructors, or dataclass with defaults. Rust: builder pattern crate or struct update syntax with '..' operator. Go: functional options pattern with variadic args. Kotlin: named parameters and default values replace most builders, apply{} for mutation. EK9: record merge operator ':~:' for incremental construction, named constructor parameters, multiple constructor overloads.
Keywords: builder, construct, incremental, named, merge, object-oriented, fluent, compile, parameter, record, pattern, step